Browse Source

Move binding as subrepo

Unknown 5 years ago
parent
commit
5c2da610a2

+ 0 - 2
bee.json

@@ -12,8 +12,6 @@
12 12
 		"models": "",
13 13
 		"others": [
14 14
 			"modules",
15
-			"$GOPATH/src/github.com/gogits/binding",
16
-			"$GOPATH/src/github.com/gogits/webdav",
17 15
 			"$GOPATH/src/github.com/gogits/logs",
18 16
 			"$GOPATH/src/github.com/gogits/git",
19 17
 			"$GOPATH/src/github.com/gogits/gfm"

+ 1 - 1
gogs.go

@@ -19,7 +19,7 @@ import (
19 19
 // Test that go1.2 tag above is included in builds. main.go refers to this definition.
20 20
 const go12tag = true
21 21
 
22
-const APP_VER = "0.2.8.0412 Alpha"
22
+const APP_VER = "0.2.8.0413 Alpha"
23 23
 
24 24
 func init() {
25 25
 	base.AppVer = APP_VER

+ 1 - 3
modules/auth/admin.go

@@ -10,8 +10,6 @@ import (
10 10
 
11 11
 	"github.com/go-martini/martini"
12 12
 
13
-	"github.com/gogits/binding"
14
-
15 13
 	"github.com/gogits/gogs/modules/base"
16 14
 	"github.com/gogits/gogs/modules/log"
17 15
 )
@@ -35,7 +33,7 @@ func (f *AdminEditUserForm) Name(field string) string {
35 33
 	return names[field]
36 34
 }
37 35
 
38
-func (f *AdminEditUserForm) Validate(errors *binding.Errors, req *http.Request, context martini.Context) {
36
+func (f *AdminEditUserForm) Validate(errors *base.BindingErrors, req *http.Request, context martini.Context) {
39 37
 	if req.Method == "GET" || errors.Count() == 0 {
40 38
 		return
41 39
 	}

+ 10 - 12
modules/auth/auth.go

@@ -11,8 +11,6 @@ import (
11 11
 
12 12
 	"github.com/go-martini/martini"
13 13
 
14
-	"github.com/gogits/binding"
15
-
16 14
 	"github.com/gogits/gogs/modules/base"
17 15
 	"github.com/gogits/gogs/modules/log"
18 16
 )
@@ -39,7 +37,7 @@ func (f *RegisterForm) Name(field string) string {
39 37
 	return names[field]
40 38
 }
41 39
 
42
-func (f *RegisterForm) Validate(errors *binding.Errors, req *http.Request, context martini.Context) {
40
+func (f *RegisterForm) Validate(errors *base.BindingErrors, req *http.Request, context martini.Context) {
43 41
 	if req.Method == "GET" || errors.Count() == 0 {
44 42
 		return
45 43
 	}
@@ -72,7 +70,7 @@ func (f *LogInForm) Name(field string) string {
72 70
 	return names[field]
73 71
 }
74 72
 
75
-func (f *LogInForm) Validate(errors *binding.Errors, req *http.Request, context martini.Context) {
73
+func (f *LogInForm) Validate(errors *base.BindingErrors, req *http.Request, context martini.Context) {
76 74
 	if req.Method == "GET" || errors.Count() == 0 {
77 75
 		return
78 76
 	}
@@ -100,7 +98,7 @@ func getMinMaxSize(field reflect.StructField) string {
100 98
 	return ""
101 99
 }
102 100
 
103
-func validate(errors *binding.Errors, data base.TmplData, form Form) {
101
+func validate(errors *base.BindingErrors, data base.TmplData, form Form) {
104 102
 	typ := reflect.TypeOf(form)
105 103
 	val := reflect.ValueOf(form)
106 104
 
@@ -121,17 +119,17 @@ func validate(errors *binding.Errors, data base.TmplData, form Form) {
121 119
 		if err, ok := errors.Fields[field.Name]; ok {
122 120
 			data["Err_"+field.Name] = true
123 121
 			switch err {
124
-			case binding.RequireError:
122
+			case base.BindingRequireError:
125 123
 				data["ErrorMsg"] = form.Name(field.Name) + " cannot be empty"
126
-			case binding.AlphaDashError:
124
+			case base.BindingAlphaDashError:
127 125
 				data["ErrorMsg"] = form.Name(field.Name) + " must be valid alpha or numeric or dash(-_) characters"
128
-			case binding.MinSizeError:
126
+			case base.BindingMinSizeError:
129 127
 				data["ErrorMsg"] = form.Name(field.Name) + " must contain at least " + getMinMaxSize(field) + " characters"
130
-			case binding.MaxSizeError:
128
+			case base.BindingMaxSizeError:
131 129
 				data["ErrorMsg"] = form.Name(field.Name) + " must contain at most " + getMinMaxSize(field) + " characters"
132
-			case binding.EmailError:
130
+			case base.BindingEmailError:
133 131
 				data["ErrorMsg"] = form.Name(field.Name) + " is not a valid e-mail address"
134
-			case binding.UrlError:
132
+			case base.BindingUrlError:
135 133
 				data["ErrorMsg"] = form.Name(field.Name) + " is not a valid URL"
136 134
 			default:
137 135
 				data["ErrorMsg"] = "Unknown error: " + err
@@ -196,7 +194,7 @@ func (f *InstallForm) Name(field string) string {
196 194
 	return names[field]
197 195
 }
198 196
 
199
-func (f *InstallForm) Validate(errors *binding.Errors, req *http.Request, context martini.Context) {
197
+func (f *InstallForm) Validate(errors *base.BindingErrors, req *http.Request, context martini.Context) {
200 198
 	if req.Method == "GET" || errors.Count() == 0 {
201 199
 		return
202 200
 	}

+ 1 - 3
modules/auth/issue.go

@@ -10,8 +10,6 @@ import (
10 10
 
11 11
 	"github.com/go-martini/martini"
12 12
 
13
-	"github.com/gogits/binding"
14
-
15 13
 	"github.com/gogits/gogs/modules/base"
16 14
 	"github.com/gogits/gogs/modules/log"
17 15
 )
@@ -31,7 +29,7 @@ func (f *CreateIssueForm) Name(field string) string {
31 29
 	return names[field]
32 30
 }
33 31
 
34
-func (f *CreateIssueForm) Validate(errors *binding.Errors, req *http.Request, context martini.Context) {
32
+func (f *CreateIssueForm) Validate(errors *base.BindingErrors, req *http.Request, context martini.Context) {
35 33
 	if req.Method == "GET" || errors.Count() == 0 {
36 34
 		return
37 35
 	}

+ 2 - 4
modules/auth/repo.go

@@ -10,8 +10,6 @@ import (
10 10
 
11 11
 	"github.com/go-martini/martini"
12 12
 
13
-	"github.com/gogits/binding"
14
-
15 13
 	"github.com/gogits/gogs/modules/base"
16 14
 	"github.com/gogits/gogs/modules/log"
17 15
 )
@@ -33,7 +31,7 @@ func (f *CreateRepoForm) Name(field string) string {
33 31
 	return names[field]
34 32
 }
35 33
 
36
-func (f *CreateRepoForm) Validate(errors *binding.Errors, req *http.Request, context martini.Context) {
34
+func (f *CreateRepoForm) Validate(errors *base.BindingErrors, req *http.Request, context martini.Context) {
37 35
 	if req.Method == "GET" || errors.Count() == 0 {
38 36
 		return
39 37
 	}
@@ -71,7 +69,7 @@ func (f *MigrateRepoForm) Name(field string) string {
71 69
 	return names[field]
72 70
 }
73 71
 
74
-func (f *MigrateRepoForm) Validate(errors *binding.Errors, req *http.Request, context martini.Context) {
72
+func (f *MigrateRepoForm) Validate(errors *base.BindingErrors, req *http.Request, context martini.Context) {
75 73
 	if req.Method == "GET" || errors.Count() == 0 {
76 74
 		return
77 75
 	}

+ 1 - 3
modules/auth/setting.go

@@ -11,8 +11,6 @@ import (
11 11
 
12 12
 	"github.com/go-martini/martini"
13 13
 
14
-	"github.com/gogits/binding"
15
-
16 14
 	"github.com/gogits/gogs/modules/base"
17 15
 	"github.com/gogits/gogs/modules/log"
18 16
 )
@@ -30,7 +28,7 @@ func (f *AddSSHKeyForm) Name(field string) string {
30 28
 	return names[field]
31 29
 }
32 30
 
33
-func (f *AddSSHKeyForm) Validate(errors *binding.Errors, req *http.Request, context martini.Context) {
31
+func (f *AddSSHKeyForm) Validate(errors *base.BindingErrors, req *http.Request, context martini.Context) {
34 32
 	data := context.Get(reflect.TypeOf(base.TmplData{})).Interface().(base.TmplData)
35 33
 	AssignForm(f, data)
36 34
 

+ 2 - 3
modules/auth/user.go

@@ -10,7 +10,6 @@ import (
10 10
 
11 11
 	"github.com/go-martini/martini"
12 12
 
13
-	"github.com/gogits/binding"
14 13
 	"github.com/gogits/session"
15 14
 
16 15
 	"github.com/gogits/gogs/models"
@@ -93,7 +92,7 @@ func (f *UpdateProfileForm) Name(field string) string {
93 92
 	return names[field]
94 93
 }
95 94
 
96
-func (f *UpdateProfileForm) Validate(errors *binding.Errors, req *http.Request, context martini.Context) {
95
+func (f *UpdateProfileForm) Validate(errors *base.BindingErrors, req *http.Request, context martini.Context) {
97 96
 	if req.Method == "GET" || errors.Count() == 0 {
98 97
 		return
99 98
 	}
@@ -126,7 +125,7 @@ func (f *UpdatePasswdForm) Name(field string) string {
126 125
 	return names[field]
127 126
 }
128 127
 
129
-func (f *UpdatePasswdForm) Validate(errors *binding.Errors, req *http.Request, context martini.Context) {
128
+func (f *UpdatePasswdForm) Validate(errors *base.BindingErrors, req *http.Request, context martini.Context) {
130 129
 	if req.Method == "GET" || errors.Count() == 0 {
131 130
 		return
132 131
 	}

+ 46 - 0
modules/base/base.go

@@ -8,3 +8,49 @@ type (
8 8
 	// Type TmplData represents data in the templates.
9 9
 	TmplData map[string]interface{}
10 10
 )
11
+
12
+// __________.__            .___.__
13
+// \______   \__| ____    __| _/|__| ____    ____
14
+//  |    |  _/  |/    \  / __ | |  |/    \  / ___\
15
+//  |    |   \  |   |  \/ /_/ | |  |   |  \/ /_/  >
16
+//  |______  /__|___|  /\____ | |__|___|  /\___  /
17
+//         \/        \/      \/         \//_____/
18
+
19
+// Errors represents the contract of the response body when the
20
+// binding step fails before getting to the application.
21
+type BindingErrors struct {
22
+	Overall map[string]string `json:"overall"`
23
+	Fields  map[string]string `json:"fields"`
24
+}
25
+
26
+// Total errors is the sum of errors with the request overall
27
+// and errors on individual fields.
28
+func (err BindingErrors) Count() int {
29
+	return len(err.Overall) + len(err.Fields)
30
+}
31
+
32
+func (this *BindingErrors) Combine(other BindingErrors) {
33
+	for key, val := range other.Fields {
34
+		if _, exists := this.Fields[key]; !exists {
35
+			this.Fields[key] = val
36
+		}
37
+	}
38
+	for key, val := range other.Overall {
39
+		if _, exists := this.Overall[key]; !exists {
40
+			this.Overall[key] = val
41
+		}
42
+	}
43
+}
44
+
45
+const (
46
+	BindingRequireError         string = "Required"
47
+	BindingAlphaDashError       string = "AlphaDash"
48
+	BindingMinSizeError         string = "MinSize"
49
+	BindingMaxSizeError         string = "MaxSize"
50
+	BindingEmailError           string = "Email"
51
+	BindingUrlError             string = "Url"
52
+	BindingDeserializationError string = "DeserializationError"
53
+	BindingIntegerTypeError     string = "IntegerTypeError"
54
+	BindingBooleanTypeError     string = "BooleanTypeError"
55
+	BindingFloatTypeError       string = "FloatTypeError"
56
+)

+ 426 - 0
modules/middleware/binding.go

@@ -0,0 +1,426 @@
1
+// Copyright 2013 The Martini Contrib Authors. All rights reserved.
2
+// Copyright 2014 The Gogs Authors. All rights reserved.
3
+// Use of this source code is governed by a MIT-style
4
+// license that can be found in the LICENSE file.
5
+
6
+package middleware
7
+
8
+import (
9
+	"encoding/json"
10
+	"fmt"
11
+	"io"
12
+	"net/http"
13
+	"reflect"
14
+	"regexp"
15
+	"strconv"
16
+	"strings"
17
+	"unicode/utf8"
18
+
19
+	"github.com/go-martini/martini"
20
+
21
+	"github.com/gogits/gogs/modules/base"
22
+)
23
+
24
+/*
25
+	To the land of Middle-ware Earth:
26
+
27
+		One func to rule them all,
28
+		One func to find them,
29
+		One func to bring them all,
30
+		And in this package BIND them.
31
+*/
32
+
33
+// Bind accepts a copy of an empty struct and populates it with
34
+// values from the request (if deserialization is successful). It
35
+// wraps up the functionality of the Form and Json middleware
36
+// according to the Content-Type of the request, and it guesses
37
+// if no Content-Type is specified. Bind invokes the ErrorHandler
38
+// middleware to bail out if errors occurred. If you want to perform
39
+// your own error handling, use Form or Json middleware directly.
40
+// An interface pointer can be added as a second argument in order
41
+// to map the struct to a specific interface.
42
+func Bind(obj interface{}, ifacePtr ...interface{}) martini.Handler {
43
+	return func(context martini.Context, req *http.Request) {
44
+		contentType := req.Header.Get("Content-Type")
45
+
46
+		if strings.Contains(contentType, "form-urlencoded") {
47
+			context.Invoke(Form(obj, ifacePtr...))
48
+		} else if strings.Contains(contentType, "multipart/form-data") {
49
+			context.Invoke(MultipartForm(obj, ifacePtr...))
50
+		} else if strings.Contains(contentType, "json") {
51
+			context.Invoke(Json(obj, ifacePtr...))
52
+		} else {
53
+			context.Invoke(Json(obj, ifacePtr...))
54
+			if getErrors(context).Count() > 0 {
55
+				context.Invoke(Form(obj, ifacePtr...))
56
+			}
57
+		}
58
+
59
+		context.Invoke(ErrorHandler)
60
+	}
61
+}
62
+
63
+// BindIgnErr will do the exactly same thing as Bind but without any
64
+// error handling, which user has freedom to deal with them.
65
+// This allows user take advantages of validation.
66
+func BindIgnErr(obj interface{}, ifacePtr ...interface{}) martini.Handler {
67
+	return func(context martini.Context, req *http.Request) {
68
+		contentType := req.Header.Get("Content-Type")
69
+
70
+		if strings.Contains(contentType, "form-urlencoded") {
71
+			context.Invoke(Form(obj, ifacePtr...))
72
+		} else if strings.Contains(contentType, "multipart/form-data") {
73
+			context.Invoke(MultipartForm(obj, ifacePtr...))
74
+		} else if strings.Contains(contentType, "json") {
75
+			context.Invoke(Json(obj, ifacePtr...))
76
+		} else {
77
+			context.Invoke(Json(obj, ifacePtr...))
78
+			if getErrors(context).Count() > 0 {
79
+				context.Invoke(Form(obj, ifacePtr...))
80
+			}
81
+		}
82
+	}
83
+}
84
+
85
+// Form is middleware to deserialize form-urlencoded data from the request.
86
+// It gets data from the form-urlencoded body, if present, or from the
87
+// query string. It uses the http.Request.ParseForm() method
88
+// to perform deserialization, then reflection is used to map each field
89
+// into the struct with the proper type. Structs with primitive slice types
90
+// (bool, float, int, string) can support deserialization of repeated form
91
+// keys, for example: key=val1&key=val2&key=val3
92
+// An interface pointer can be added as a second argument in order
93
+// to map the struct to a specific interface.
94
+func Form(formStruct interface{}, ifacePtr ...interface{}) martini.Handler {
95
+	return func(context martini.Context, req *http.Request) {
96
+		ensureNotPointer(formStruct)
97
+		formStruct := reflect.New(reflect.TypeOf(formStruct))
98
+		errors := newErrors()
99
+		parseErr := req.ParseForm()
100
+
101
+		// Format validation of the request body or the URL would add considerable overhead,
102
+		// and ParseForm does not complain when URL encoding is off.
103
+		// Because an empty request body or url can also mean absence of all needed values,
104
+		// it is not in all cases a bad request, so let's return 422.
105
+		if parseErr != nil {
106
+			errors.Overall[base.BindingDeserializationError] = parseErr.Error()
107
+		}
108
+
109
+		mapForm(formStruct, req.Form, errors)
110
+
111
+		validateAndMap(formStruct, context, errors, ifacePtr...)
112
+	}
113
+}
114
+
115
+func MultipartForm(formStruct interface{}, ifacePtr ...interface{}) martini.Handler {
116
+	return func(context martini.Context, req *http.Request) {
117
+		ensureNotPointer(formStruct)
118
+		formStruct := reflect.New(reflect.TypeOf(formStruct))
119
+		errors := newErrors()
120
+
121
+		// Workaround for multipart forms returning nil instead of an error
122
+		// when content is not multipart
123
+		// https://code.google.com/p/go/issues/detail?id=6334
124
+		multipartReader, err := req.MultipartReader()
125
+		if err != nil {
126
+			errors.Overall[base.BindingDeserializationError] = err.Error()
127
+		} else {
128
+			form, parseErr := multipartReader.ReadForm(MaxMemory)
129
+
130
+			if parseErr != nil {
131
+				errors.Overall[base.BindingDeserializationError] = parseErr.Error()
132
+			}
133
+
134
+			req.MultipartForm = form
135
+		}
136
+
137
+		mapForm(formStruct, req.MultipartForm.Value, errors)
138
+
139
+		validateAndMap(formStruct, context, errors, ifacePtr...)
140
+	}
141
+}
142
+
143
+// Json is middleware to deserialize a JSON payload from the request
144
+// into the struct that is passed in. The resulting struct is then
145
+// validated, but no error handling is actually performed here.
146
+// An interface pointer can be added as a second argument in order
147
+// to map the struct to a specific interface.
148
+func Json(jsonStruct interface{}, ifacePtr ...interface{}) martini.Handler {
149
+	return func(context martini.Context, req *http.Request) {
150
+		ensureNotPointer(jsonStruct)
151
+		jsonStruct := reflect.New(reflect.TypeOf(jsonStruct))
152
+		errors := newErrors()
153
+
154
+		if req.Body != nil {
155
+			defer req.Body.Close()
156
+		}
157
+
158
+		if err := json.NewDecoder(req.Body).Decode(jsonStruct.Interface()); err != nil && err != io.EOF {
159
+			errors.Overall[base.BindingDeserializationError] = err.Error()
160
+		}
161
+
162
+		validateAndMap(jsonStruct, context, errors, ifacePtr...)
163
+	}
164
+}
165
+
166
+// Validate is middleware to enforce required fields. If the struct
167
+// passed in is a Validator, then the user-defined Validate method
168
+// is executed, and its errors are mapped to the context. This middleware
169
+// performs no error handling: it merely detects them and maps them.
170
+func Validate(obj interface{}) martini.Handler {
171
+	return func(context martini.Context, req *http.Request) {
172
+		errors := newErrors()
173
+		validateStruct(errors, obj)
174
+
175
+		if validator, ok := obj.(Validator); ok {
176
+			validator.Validate(errors, req, context)
177
+		}
178
+		context.Map(*errors)
179
+	}
180
+}
181
+
182
+var (
183
+	alphaDashPattern = regexp.MustCompile("[^\\d\\w-_]")
184
+	emailPattern     = regexp.MustCompile("[\\w!#$%&'*+/=?^_`{|}~-]+(?:\\.[\\w!#$%&'*+/=?^_`{|}~-]+)*@(?:[\\w](?:[\\w-]*[\\w])?\\.)+[a-zA-Z0-9](?:[\\w-]*[\\w])?")
185
+	urlPattern       = regexp.MustCompile(`(http|https):\/\/[\w\-_]+(\.[\w\-_]+)+([\w\-\.,@?^=%&:/~\+#]*[\w\-\@?^=%&/~\+#])?`)
186
+)
187
+
188
+func validateStruct(errors *base.BindingErrors, obj interface{}) {
189
+	typ := reflect.TypeOf(obj)
190
+	val := reflect.ValueOf(obj)
191
+
192
+	if typ.Kind() == reflect.Ptr {
193
+		typ = typ.Elem()
194
+		val = val.Elem()
195
+	}
196
+
197
+	for i := 0; i < typ.NumField(); i++ {
198
+		field := typ.Field(i)
199
+
200
+		// Allow ignored fields in the struct
201
+		if field.Tag.Get("form") == "-" {
202
+			continue
203
+		}
204
+
205
+		fieldValue := val.Field(i).Interface()
206
+		if field.Type.Kind() == reflect.Struct {
207
+			validateStruct(errors, fieldValue)
208
+			continue
209
+		}
210
+
211
+		zero := reflect.Zero(field.Type).Interface()
212
+
213
+		// Match rules.
214
+		for _, rule := range strings.Split(field.Tag.Get("binding"), ";") {
215
+			if len(rule) == 0 {
216
+				continue
217
+			}
218
+
219
+			switch {
220
+			case rule == "Required":
221
+				if reflect.DeepEqual(zero, fieldValue) {
222
+					errors.Fields[field.Name] = base.BindingRequireError
223
+					break
224
+				}
225
+			case rule == "AlphaDash":
226
+				if alphaDashPattern.MatchString(fmt.Sprintf("%v", fieldValue)) {
227
+					errors.Fields[field.Name] = base.BindingAlphaDashError
228
+					break
229
+				}
230
+			case strings.HasPrefix(rule, "MinSize("):
231
+				min, err := strconv.Atoi(rule[8 : len(rule)-1])
232
+				if err != nil {
233
+					errors.Overall["MinSize"] = err.Error()
234
+					break
235
+				}
236
+				if str, ok := fieldValue.(string); ok && utf8.RuneCountInString(str) < min {
237
+					errors.Fields[field.Name] = base.BindingMinSizeError
238
+					break
239
+				}
240
+				v := reflect.ValueOf(fieldValue)
241
+				if v.Kind() == reflect.Slice && v.Len() < min {
242
+					errors.Fields[field.Name] = base.BindingMinSizeError
243
+					break
244
+				}
245
+			case strings.HasPrefix(rule, "MaxSize("):
246
+				max, err := strconv.Atoi(rule[8 : len(rule)-1])
247
+				if err != nil {
248
+					errors.Overall["MaxSize"] = err.Error()
249
+					break
250
+				}
251
+				if str, ok := fieldValue.(string); ok && utf8.RuneCountInString(str) > max {
252
+					errors.Fields[field.Name] = base.BindingMaxSizeError
253
+					break
254
+				}
255
+				v := reflect.ValueOf(fieldValue)
256
+				if v.Kind() == reflect.Slice && v.Len() > max {
257
+					errors.Fields[field.Name] = base.BindingMinSizeError
258
+					break
259
+				}
260
+			case rule == "Email":
261
+				if !emailPattern.MatchString(fmt.Sprintf("%v", fieldValue)) {
262
+					errors.Fields[field.Name] = base.BindingEmailError
263
+					break
264
+				}
265
+			case rule == "Url":
266
+				if !urlPattern.MatchString(fmt.Sprintf("%v", fieldValue)) {
267
+					errors.Fields[field.Name] = base.BindingUrlError
268
+					break
269
+				}
270
+			}
271
+		}
272
+	}
273
+}
274
+
275
+func mapForm(formStruct reflect.Value, form map[string][]string, errors *base.BindingErrors) {
276
+	typ := formStruct.Elem().Type()
277
+
278
+	for i := 0; i < typ.NumField(); i++ {
279
+		typeField := typ.Field(i)
280
+		if inputFieldName := typeField.Tag.Get("form"); inputFieldName != "" {
281
+			structField := formStruct.Elem().Field(i)
282
+			if !structField.CanSet() {
283
+				continue
284
+			}
285
+
286
+			inputValue, exists := form[inputFieldName]
287
+
288
+			if !exists {
289
+				continue
290
+			}
291
+
292
+			numElems := len(inputValue)
293
+			if structField.Kind() == reflect.Slice && numElems > 0 {
294
+				sliceOf := structField.Type().Elem().Kind()
295
+				slice := reflect.MakeSlice(structField.Type(), numElems, numElems)
296
+				for i := 0; i < numElems; i++ {
297
+					setWithProperType(sliceOf, inputValue[i], slice.Index(i), inputFieldName, errors)
298
+				}
299
+				formStruct.Elem().Field(i).Set(slice)
300
+			} else {
301
+				setWithProperType(typeField.Type.Kind(), inputValue[0], structField, inputFieldName, errors)
302
+			}
303
+		}
304
+	}
305
+}
306
+
307
+// ErrorHandler simply counts the number of errors in the
308
+// context and, if more than 0, writes a 400 Bad Request
309
+// response and a JSON payload describing the errors with
310
+// the "Content-Type" set to "application/json".
311
+// Middleware remaining on the stack will not even see the request
312
+// if, by this point, there are any errors.
313
+// This is a "default" handler, of sorts, and you are
314
+// welcome to use your own instead. The Bind middleware
315
+// invokes this automatically for convenience.
316
+func ErrorHandler(errs base.BindingErrors, resp http.ResponseWriter) {
317
+	if errs.Count() > 0 {
318
+		resp.Header().Set("Content-Type", "application/json; charset=utf-8")
319
+		if _, ok := errs.Overall[base.BindingDeserializationError]; ok {
320
+			resp.WriteHeader(http.StatusBadRequest)
321
+		} else {
322
+			resp.WriteHeader(422)
323
+		}
324
+		errOutput, _ := json.Marshal(errs)
325
+		resp.Write(errOutput)
326
+		return
327
+	}
328
+}
329
+
330
+// This sets the value in a struct of an indeterminate type to the
331
+// matching value from the request (via Form middleware) in the
332
+// same type, so that not all deserialized values have to be strings.
333
+// Supported types are string, int, float, and bool.
334
+func setWithProperType(valueKind reflect.Kind, val string, structField reflect.Value, nameInTag string, errors *base.BindingErrors) {
335
+	switch valueKind {
336
+	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
337
+		if val == "" {
338
+			val = "0"
339
+		}
340
+		intVal, err := strconv.ParseInt(val, 10, 64)
341
+		if err != nil {
342
+			errors.Fields[nameInTag] = base.BindingIntegerTypeError
343
+		} else {
344
+			structField.SetInt(intVal)
345
+		}
346
+	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
347
+		if val == "" {
348
+			val = "0"
349
+		}
350
+		uintVal, err := strconv.ParseUint(val, 10, 64)
351
+		if err != nil {
352
+			errors.Fields[nameInTag] = base.BindingIntegerTypeError
353
+		} else {
354
+			structField.SetUint(uintVal)
355
+		}
356
+	case reflect.Bool:
357
+		structField.SetBool(val == "on")
358
+	case reflect.Float32:
359
+		if val == "" {
360
+			val = "0.0"
361
+		}
362
+		floatVal, err := strconv.ParseFloat(val, 32)
363
+		if err != nil {
364
+			errors.Fields[nameInTag] = base.BindingFloatTypeError
365
+		} else {
366
+			structField.SetFloat(floatVal)
367
+		}
368
+	case reflect.Float64:
369
+		if val == "" {
370
+			val = "0.0"
371
+		}
372
+		floatVal, err := strconv.ParseFloat(val, 64)
373
+		if err != nil {
374
+			errors.Fields[nameInTag] = base.BindingFloatTypeError
375
+		} else {
376
+			structField.SetFloat(floatVal)
377
+		}
378
+	case reflect.String:
379
+		structField.SetString(val)
380
+	}
381
+}
382
+
383
+// Don't pass in pointers to bind to. Can lead to bugs. See:
384
+// https://github.com/codegangsta/martini-contrib/issues/40
385
+// https://github.com/codegangsta/martini-contrib/pull/34#issuecomment-29683659
386
+func ensureNotPointer(obj interface{}) {
387
+	if reflect.TypeOf(obj).Kind() == reflect.Ptr {
388
+		panic("Pointers are not accepted as binding models")
389
+	}
390
+}
391
+
392
+// Performs validation and combines errors from validation
393
+// with errors from deserialization, then maps both the
394
+// resulting struct and the errors to the context.
395
+func validateAndMap(obj reflect.Value, context martini.Context, errors *base.BindingErrors, ifacePtr ...interface{}) {
396
+	context.Invoke(Validate(obj.Interface()))
397
+	errors.Combine(getErrors(context))
398
+	context.Map(*errors)
399
+	context.Map(obj.Elem().Interface())
400
+	if len(ifacePtr) > 0 {
401
+		context.MapTo(obj.Elem().Interface(), ifacePtr[0])
402
+	}
403
+}
404
+
405
+func newErrors() *base.BindingErrors {
406
+	return &base.BindingErrors{make(map[string]string), make(map[string]string)}
407
+}
408
+
409
+func getErrors(context martini.Context) base.BindingErrors {
410
+	return context.Get(reflect.TypeOf(base.BindingErrors{})).Interface().(base.BindingErrors)
411
+}
412
+
413
+type (
414
+	// Implement the Validator interface to define your own input
415
+	// validation before the request even gets to your application.
416
+	// The Validate method will be executed during the validation phase.
417
+	Validator interface {
418
+		Validate(*base.BindingErrors, *http.Request, martini.Context)
419
+	}
420
+)
421
+
422
+var (
423
+	// Maximum amount of memory to use when parsing a multipart form.
424
+	// Set this to whatever value you prefer; default is 10 MB.
425
+	MaxMemory = int64(1024 * 1024 * 10)
426
+)

+ 701 - 0
modules/middleware/binding_test.go

@@ -0,0 +1,701 @@
1
+// Copyright 2013 The Martini Contrib Authors. All rights reserved.
2
+// Copyright 2014 The Gogs Authors. All rights reserved.
3
+// Use of this source code is governed by a MIT-style
4
+// license that can be found in the LICENSE file.
5
+
6
+package middleware
7
+
8
+import (
9
+	"bytes"
10
+	"mime/multipart"
11
+	"net/http"
12
+	"net/http/httptest"
13
+	"strconv"
14
+	"strings"
15
+	"testing"
16
+
17
+	"github.com/codegangsta/martini"
18
+)
19
+
20
+func TestBind(t *testing.T) {
21
+	testBind(t, false)
22
+}
23
+
24
+func TestBindWithInterface(t *testing.T) {
25
+	testBind(t, true)
26
+}
27
+
28
+func TestMultipartBind(t *testing.T) {
29
+	index := 0
30
+	for test, expectStatus := range bindMultipartTests {
31
+		handler := func(post BlogPost, errors Errors) {
32
+			handle(test, t, index, post, errors)
33
+		}
34
+		recorder := testMultipart(t, test, Bind(BlogPost{}), handler, index)
35
+
36
+		if recorder.Code != expectStatus {
37
+			t.Errorf("On test case %v, got status code %d but expected %d", test, recorder.Code, expectStatus)
38
+		}
39
+
40
+		index++
41
+	}
42
+}
43
+
44
+func TestForm(t *testing.T) {
45
+	testForm(t, false)
46
+}
47
+
48
+func TestFormWithInterface(t *testing.T) {
49
+	testForm(t, true)
50
+}
51
+
52
+func TestEmptyForm(t *testing.T) {
53
+	testEmptyForm(t)
54
+}
55
+
56
+func TestMultipartForm(t *testing.T) {
57
+	for index, test := range multipartformTests {
58
+		handler := func(post BlogPost, errors Errors) {
59
+			handle(test, t, index, post, errors)
60
+		}
61
+		testMultipart(t, test, MultipartForm(BlogPost{}), handler, index)
62
+	}
63
+}
64
+
65
+func TestMultipartFormWithInterface(t *testing.T) {
66
+	for index, test := range multipartformTests {
67
+		handler := func(post Modeler, errors Errors) {
68
+			post.Create(test, t, index)
69
+		}
70
+		testMultipart(t, test, MultipartForm(BlogPost{}, (*Modeler)(nil)), handler, index)
71
+	}
72
+}
73
+
74
+func TestJson(t *testing.T) {
75
+	testJson(t, false)
76
+}
77
+
78
+func TestJsonWithInterface(t *testing.T) {
79
+	testJson(t, true)
80
+}
81
+
82
+func TestEmptyJson(t *testing.T) {
83
+	testEmptyJson(t)
84
+}
85
+
86
+func TestValidate(t *testing.T) {
87
+	handlerMustErr := func(errors Errors) {
88
+		if errors.Count() == 0 {
89
+			t.Error("Expected at least one error, got 0")
90
+		}
91
+	}
92
+	handlerNoErr := func(errors Errors) {
93
+		if errors.Count() > 0 {
94
+			t.Error("Expected no errors, got", errors.Count())
95
+		}
96
+	}
97
+
98
+	performValidationTest(&BlogPost{"", "...", 0, 0, []int{}}, handlerMustErr, t)
99
+	performValidationTest(&BlogPost{"Good Title", "Good content", 0, 0, []int{}}, handlerNoErr, t)
100
+
101
+	performValidationTest(&User{Name: "Jim", Home: Address{"", ""}}, handlerMustErr, t)
102
+	performValidationTest(&User{Name: "Jim", Home: Address{"required", ""}}, handlerNoErr, t)
103
+}
104
+
105
+func handle(test testCase, t *testing.T, index int, post BlogPost, errors Errors) {
106
+	assertEqualField(t, "Title", index, test.ref.Title, post.Title)
107
+	assertEqualField(t, "Content", index, test.ref.Content, post.Content)
108
+	assertEqualField(t, "Views", index, test.ref.Views, post.Views)
109
+
110
+	for i := range test.ref.Multiple {
111
+		if i >= len(post.Multiple) {
112
+			t.Errorf("Expected: %v (size %d) to have same size as: %v (size %d)", post.Multiple, len(post.Multiple), test.ref.Multiple, len(test.ref.Multiple))
113
+			break
114
+		}
115
+		if test.ref.Multiple[i] != post.Multiple[i] {
116
+			t.Errorf("Expected: %v to deep equal: %v", post.Multiple, test.ref.Multiple)
117
+			break
118
+		}
119
+	}
120
+
121
+	if test.ok && errors.Count() > 0 {
122
+		t.Errorf("%+v should be OK (0 errors), but had errors: %+v", test, errors)
123
+	} else if !test.ok && errors.Count() == 0 {
124
+		t.Errorf("%+v should have errors, but was OK (0 errors): %+v", test)
125
+	}
126
+}
127
+
128
+func handleEmpty(test emptyPayloadTestCase, t *testing.T, index int, section BlogSection, errors Errors) {
129
+	assertEqualField(t, "Title", index, test.ref.Title, section.Title)
130
+	assertEqualField(t, "Content", index, test.ref.Content, section.Content)
131
+
132
+	if test.ok && errors.Count() > 0 {
133
+		t.Errorf("%+v should be OK (0 errors), but had errors: %+v", test, errors)
134
+	} else if !test.ok && errors.Count() == 0 {
135
+		t.Errorf("%+v should have errors, but was OK (0 errors): %+v", test)
136
+	}
137
+}
138
+
139
+func testBind(t *testing.T, withInterface bool) {
140
+	index := 0
141
+	for test, expectStatus := range bindTests {
142
+		m := martini.Classic()
143
+		recorder := httptest.NewRecorder()
144
+		handler := func(post BlogPost, errors Errors) { handle(test, t, index, post, errors) }
145
+		binding := Bind(BlogPost{})
146
+
147
+		if withInterface {
148
+			handler = func(post BlogPost, errors Errors) {
149
+				post.Create(test, t, index)
150
+			}
151
+			binding = Bind(BlogPost{}, (*Modeler)(nil))
152
+		}
153
+
154
+		switch test.method {
155
+		case "GET":
156
+			m.Get(route, binding, handler)
157
+		case "POST":
158
+			m.Post(route, binding, handler)
159
+		}
160
+
161
+		req, err := http.NewRequest(test.method, test.path, strings.NewReader(test.payload))
162
+		req.Header.Add("Content-Type", test.contentType)
163
+
164
+		if err != nil {
165
+			t.Error(err)
166
+		}
167
+		m.ServeHTTP(recorder, req)
168
+
169
+		if recorder.Code != expectStatus {
170
+			t.Errorf("On test case %v, got status code %d but expected %d", test, recorder.Code, expectStatus)
171
+		}
172
+
173
+		index++
174
+	}
175
+}
176
+
177
+func testJson(t *testing.T, withInterface bool) {
178
+	for index, test := range jsonTests {
179
+		recorder := httptest.NewRecorder()
180
+		handler := func(post BlogPost, errors Errors) { handle(test, t, index, post, errors) }
181
+		binding := Json(BlogPost{})
182
+
183
+		if withInterface {
184
+			handler = func(post BlogPost, errors Errors) {
185
+				post.Create(test, t, index)
186
+			}
187
+			binding = Bind(BlogPost{}, (*Modeler)(nil))
188
+		}
189
+
190
+		m := martini.Classic()
191
+		switch test.method {
192
+		case "GET":
193
+			m.Get(route, binding, handler)
194
+		case "POST":
195
+			m.Post(route, binding, handler)
196
+		case "PUT":
197
+			m.Put(route, binding, handler)
198
+		case "DELETE":
199
+			m.Delete(route, binding, handler)
200
+		}
201
+
202
+		req, err := http.NewRequest(test.method, route, strings.NewReader(test.payload))
203
+		if err != nil {
204
+			t.Error(err)
205
+		}
206
+		m.ServeHTTP(recorder, req)
207
+	}
208
+}
209
+
210
+func testEmptyJson(t *testing.T) {
211
+	for index, test := range emptyPayloadTests {
212
+		recorder := httptest.NewRecorder()
213
+		handler := func(section BlogSection, errors Errors) { handleEmpty(test, t, index, section, errors) }
214
+		binding := Json(BlogSection{})
215
+
216
+		m := martini.Classic()
217
+		switch test.method {
218
+		case "GET":
219
+			m.Get(route, binding, handler)
220
+		case "POST":
221
+			m.Post(route, binding, handler)
222
+		case "PUT":
223
+			m.Put(route, binding, handler)
224
+		case "DELETE":
225
+			m.Delete(route, binding, handler)
226
+		}
227
+
228
+		req, err := http.NewRequest(test.method, route, strings.NewReader(test.payload))
229
+		if err != nil {
230
+			t.Error(err)
231
+		}
232
+		m.ServeHTTP(recorder, req)
233
+	}
234
+}
235
+
236
+func testForm(t *testing.T, withInterface bool) {
237
+	for index, test := range formTests {
238
+		recorder := httptest.NewRecorder()
239
+		handler := func(post BlogPost, errors Errors) { handle(test, t, index, post, errors) }
240
+		binding := Form(BlogPost{})
241
+
242
+		if withInterface {
243
+			handler = func(post BlogPost, errors Errors) {
244
+				post.Create(test, t, index)
245
+			}
246
+			binding = Form(BlogPost{}, (*Modeler)(nil))
247
+		}
248
+
249
+		m := martini.Classic()
250
+		switch test.method {
251
+		case "GET":
252
+			m.Get(route, binding, handler)
253
+		case "POST":
254
+			m.Post(route, binding, handler)
255
+		}
256
+
257
+		req, err := http.NewRequest(test.method, test.path, nil)
258
+		if err != nil {
259
+			t.Error(err)
260
+		}
261
+		m.ServeHTTP(recorder, req)
262
+	}
263
+}
264
+
265
+func testEmptyForm(t *testing.T) {
266
+	for index, test := range emptyPayloadTests {
267
+		recorder := httptest.NewRecorder()
268
+		handler := func(section BlogSection, errors Errors) { handleEmpty(test, t, index, section, errors) }
269
+		binding := Form(BlogSection{})
270
+
271
+		m := martini.Classic()
272
+		switch test.method {
273
+		case "GET":
274
+			m.Get(route, binding, handler)
275
+		case "POST":
276
+			m.Post(route, binding, handler)
277
+		}
278
+
279
+		req, err := http.NewRequest(test.method, test.path, nil)
280
+		if err != nil {
281
+			t.Error(err)
282
+		}
283
+		m.ServeHTTP(recorder, req)
284
+	}
285
+}
286
+
287
+func testMultipart(t *testing.T, test testCase, middleware martini.Handler, handler martini.Handler, index int) *httptest.ResponseRecorder {
288
+	recorder := httptest.NewRecorder()
289
+
290
+	m := martini.Classic()
291
+	m.Post(route, middleware, handler)
292
+
293
+	body := &bytes.Buffer{}
294
+	writer := multipart.NewWriter(body)
295
+	writer.WriteField("title", test.ref.Title)
296
+	writer.WriteField("content", test.ref.Content)
297
+	writer.WriteField("views", strconv.Itoa(test.ref.Views))
298
+	if len(test.ref.Multiple) != 0 {
299
+		for _, value := range test.ref.Multiple {
300
+			writer.WriteField("multiple", strconv.Itoa(value))
301
+		}
302
+	}
303
+
304
+	req, err := http.NewRequest(test.method, test.path, body)
305
+	req.Header.Add("Content-Type", writer.FormDataContentType())
306
+
307
+	if err != nil {
308
+		t.Error(err)
309
+	}
310
+
311
+	err = writer.Close()
312
+	if err != nil {
313
+		t.Error(err)
314
+	}
315
+
316
+	m.ServeHTTP(recorder, req)
317
+
318
+	return recorder
319
+}
320
+
321
+func assertEqualField(t *testing.T, fieldname string, testcasenumber int, expected interface{}, got interface{}) {
322
+	if expected != got {
323
+		t.Errorf("%s: expected=%s, got=%s in test case %d\n", fieldname, expected, got, testcasenumber)
324
+	}
325
+}
326
+
327
+func performValidationTest(data interface{}, handler func(Errors), t *testing.T) {
328
+	recorder := httptest.NewRecorder()
329
+	m := martini.Classic()
330
+	m.Get(route, Validate(data), handler)
331
+
332
+	req, err := http.NewRequest("GET", route, nil)
333
+	if err != nil {
334
+		t.Error("HTTP error:", err)
335
+	}
336
+
337
+	m.ServeHTTP(recorder, req)
338
+}
339
+
340
+func (self BlogPost) Validate(errors *Errors, req *http.Request) {
341
+	if len(self.Title) < 4 {
342
+		errors.Fields["Title"] = "Too short; minimum 4 characters"
343
+	}
344
+	if len(self.Content) > 1024 {
345
+		errors.Fields["Content"] = "Too long; maximum 1024 characters"
346
+	}
347
+	if len(self.Content) < 5 {
348
+		errors.Fields["Content"] = "Too short; minimum 5 characters"
349
+	}
350
+}
351
+
352
+func (self BlogPost) Create(test testCase, t *testing.T, index int) {
353
+	assertEqualField(t, "Title", index, test.ref.Title, self.Title)
354
+	assertEqualField(t, "Content", index, test.ref.Content, self.Content)
355
+	assertEqualField(t, "Views", index, test.ref.Views, self.Views)
356
+
357
+	for i := range test.ref.Multiple {
358
+		if i >= len(self.Multiple) {
359
+			t.Errorf("Expected: %v (size %d) to have same size as: %v (size %d)", self.Multiple, len(self.Multiple), test.ref.Multiple, len(test.ref.Multiple))
360
+			break
361
+		}
362
+		if test.ref.Multiple[i] != self.Multiple[i] {
363
+			t.Errorf("Expected: %v to deep equal: %v", self.Multiple, test.ref.Multiple)
364
+			break
365
+		}
366
+	}
367
+}
368
+
369
+func (self BlogSection) Create(test emptyPayloadTestCase, t *testing.T, index int) {
370
+	// intentionally left empty
371
+}
372
+
373
+type (
374
+	testCase struct {
375
+		method      string
376
+		path        string
377
+		payload     string
378
+		contentType string
379
+		ok          bool
380
+		ref         *BlogPost
381
+	}
382
+
383
+	emptyPayloadTestCase struct {
384
+		method      string
385
+		path        string
386
+		payload     string
387
+		contentType string
388
+		ok          bool
389
+		ref         *BlogSection
390
+	}
391
+
392
+	Modeler interface {
393
+		Create(test testCase, t *testing.T, index int)
394
+	}
395
+
396
+	BlogPost struct {
397
+		Title    string `form:"title" json:"title" binding:"required"`
398
+		Content  string `form:"content" json:"content"`
399
+		Views    int    `form:"views" json:"views"`
400
+		internal int    `form:"-"`
401
+		Multiple []int  `form:"multiple"`
402
+	}
403
+
404
+	BlogSection struct {
405
+		Title   string `form:"title" json:"title"`
406
+		Content string `form:"content" json:"content"`
407
+	}
408
+
409
+	User struct {
410
+		Name string  `json:"name" binding:"required"`
411
+		Home Address `json:"address" binding:"required"`
412
+	}
413
+
414
+	Address struct {
415
+		Street1 string `json:"street1" binding:"required"`
416
+		Street2 string `json:"street2"`
417
+	}
418
+)
419
+
420
+var (
421
+	bindTests = map[testCase]int{
422
+		// These should bail at the deserialization/binding phase
423
+		testCase{
424
+			"POST",
425
+			path,
426
+			`{ bad JSON `,
427
+			"application/json",
428
+			false,
429
+			new(BlogPost),
430
+		}: http.StatusBadRequest,
431
+		testCase{
432
+			"POST",
433
+			path,
434
+			`not multipart but has content-type`,
435
+			"multipart/form-data",
436
+			false,
437
+			new(BlogPost),
438
+		}: http.StatusBadRequest,
439
+		testCase{
440
+			"POST",
441
+			path,
442
+			`no content-type and not URL-encoded or JSON"`,
443
+			"",
444
+			false,
445
+			new(BlogPost),
446
+		}: http.StatusBadRequest,
447
+
448
+		// These should deserialize, then bail at the validation phase
449
+		testCase{
450
+			"POST",
451
+			path + "?title= This is wrong  ",
452
+			`not URL-encoded but has content-type`,
453
+			"x-www-form-urlencoded",
454
+			false,
455
+			new(BlogPost),
456
+		}: 422, // according to comments in Form() -> although the request is not url encoded, ParseForm does not complain
457
+		testCase{
458
+			"GET",
459
+			path + "?content=This+is+the+content",
460
+			``,
461
+			"x-www-form-urlencoded",
462
+			false,
463
+			&BlogPost{Title: "", Content: "This is the content"},
464
+		}: 422,
465
+		testCase{
466
+			"GET",
467
+			path + "",
468
+			`{"content":"", "title":"Blog Post Title"}`,
469
+			"application/json",
470
+			false,
471
+			&BlogPost{Title: "Blog Post Title", Content: ""},
472
+		}: 422,
473
+
474
+		// These should succeed
475
+		testCase{
476
+			"GET",
477
+			path + "",
478
+			`{"content":"This is the content", "title":"Blog Post Title"}`,
479
+			"application/json",
480
+			true,
481
+			&BlogPost{Title: "Blog Post Title", Content: "This is the content"},
482
+		}: http.StatusOK,
483
+		testCase{
484
+			"GET",
485
+			path + "?content=This+is+the+content&title=Blog+Post+Title",
486
+			``,
487
+			"",
488
+			true,
489
+			&BlogPost{Title: "Blog Post Title", Content: "This is the content"},
490
+		}: http.StatusOK,
491
+		testCase{
492
+			"GET",
493
+			path + "?content=This is the content&title=Blog+Post+Title",
494
+			`{"content":"This is the content", "title":"Blog Post Title"}`,
495
+			"",
496
+			true,
497
+			&BlogPost{Title: "Blog Post Title", Content: "This is the content"},
498
+		}: http.StatusOK,
499
+		testCase{
500
+			"GET",
501
+			path + "",
502
+			`{"content":"This is the content", "title":"Blog Post Title"}`,
503
+			"",
504
+			true,
505
+			&BlogPost{Title: "Blog Post Title", Content: "This is the content"},
506
+		}: http.StatusOK,
507
+	}
508
+
509
+	bindMultipartTests = map[testCase]int{
510
+		// This should deserialize, then bail at the validation phase
511
+		testCase{
512
+			"POST",
513
+			path,
514
+			"",
515
+			"multipart/form-data",
516
+			false,
517
+			&BlogPost{Title: "", Content: "This is the content"},
518
+		}: 422,
519
+		// This should succeed
520
+		testCase{
521
+			"POST",
522
+			path,
523
+			"",
524
+			"multipart/form-data",
525
+			true,
526
+			&BlogPost{Title: "This is the Title", Content: "This is the content"},
527
+		}: http.StatusOK,
528
+	}
529
+
530
+	formTests = []testCase{
531
+		{
532
+			"GET",
533
+			path + "?content=This is the content",
534
+			"",
535
+			"",
536
+			false,
537
+			&BlogPost{Title: "", Content: "This is the content"},
538
+		},
539
+		{
540
+			"POST",
541
+			path + "?content=This+is+the+content&title=Blog+Post+Title&views=3",
542
+			"",
543
+			"",
544
+			false, // false because POST requests should have a body, not just a query string
545
+			&BlogPost{Title: "Blog Post Title", Content: "This is the content", Views: 3},
546
+		},
547
+		{
548
+			"GET",
549
+			path + "?content=This+is+the+content&title=Blog+Post+Title&views=3&multiple=5&multiple=10&multiple=15&multiple=20",
550
+			"",
551
+			"",
552
+			true,
553
+			&BlogPost{Title: "Blog Post Title", Content: "This is the content", Views: 3, Multiple: []int{5, 10, 15, 20}},
554
+		},
555
+	}
556
+
557
+	multipartformTests = []testCase{
558
+		{
559
+			"POST",
560
+			path,
561
+			"",
562
+			"multipart/form-data",
563
+			false,
564
+			&BlogPost{Title: "", Content: "This is the content"},
565
+		},
566
+		{
567
+			"POST",
568
+			path,
569
+			"",
570
+			"multipart/form-data",
571
+			false,
572
+			&BlogPost{Title: "Blog Post Title", Views: 3},
573
+		},
574
+		{
575
+			"POST",
576
+			path,
577
+			"",
578
+			"multipart/form-data",
579
+			true,
580
+			&BlogPost{Title: "Blog Post Title", Content: "This is the content", Views: 3, Multiple: []int{5, 10, 15, 20}},
581
+		},
582
+	}
583
+
584
+	emptyPayloadTests = []emptyPayloadTestCase{
585
+		{
586
+			"GET",
587
+			"",
588
+			"",
589
+			"",
590
+			true,
591
+			&BlogSection{},
592
+		},
593
+		{
594
+			"POST",
595
+			"",
596
+			"",
597
+			"",
598
+			true,
599
+			&BlogSection{},
600
+		},
601
+		{
602
+			"PUT",
603
+			"",
604
+			"",
605
+			"",
606
+			true,
607
+			&BlogSection{},
608
+		},
609
+		{
610
+			"DELETE",
611
+			"",
612
+			"",
613
+			"",
614
+			true,
615
+			&BlogSection{},
616
+		},
617
+	}
618
+
619
+	jsonTests = []testCase{
620
+		// bad requests
621
+		{
622
+			"GET",
623
+			"",
624
+			`{blah blah blah}`,
625
+			"",
626
+			false,
627
+			&BlogPost{},
628
+		},
629
+		{
630
+			"POST",
631
+			"",
632
+			`{asdf}`,
633
+			"",
634
+			false,
635
+			&BlogPost{},
636
+		},
637
+		{
638
+			"PUT",
639
+			"",
640
+			`{blah blah blah}`,
641
+			"",
642
+			false,
643
+			&BlogPost{},
644
+		},
645
+		{
646
+			"DELETE",
647
+			"",
648
+			`{;sdf _SDf- }`,
649
+			"",
650
+			false,
651
+			&BlogPost{},
652
+		},
653
+
654
+		// Valid-JSON requests
655
+		{
656
+			"GET",
657
+			"",
658
+			`{"content":"This is the content"}`,
659
+			"",
660
+			false,
661
+			&BlogPost{Title: "", Content: "This is the content"},
662
+		},
663
+		{
664
+			"POST",
665
+			"",
666
+			`{}`,
667
+			"application/json",
668
+			false,
669
+			&BlogPost{Title: "", Content: ""},
670
+		},
671
+		{
672
+			"POST",
673
+			"",
674
+			`{"content":"This is the content", "title":"Blog Post Title"}`,
675
+			"",
676
+			true,
677
+			&BlogPost{Title: "Blog Post Title", Content: "This is the content"},
678
+		},
679
+		{
680
+			"PUT",
681
+			"",
682
+			`{"content":"This is the content", "title":"Blog Post Title"}`,
683
+			"",
684
+			true,
685
+			&BlogPost{Title: "Blog Post Title", Content: "This is the content"},
686
+		},
687
+		{
688
+			"DELETE",
689
+			"",
690
+			`{"content":"This is the content", "title":"Blog Post Title"}`,
691
+			"",
692
+			true,
693
+			&BlogPost{Title: "Blog Post Title", Content: "This is the content"},
694
+		},
695
+	}
696
+)
697
+
698
+const (
699
+	route = "/blogposts/create"
700
+	path  = "http://localhost:3000" + route
701
+)

+ 196 - 0
routers/user/home.go

@@ -0,0 +1,196 @@
1
+// Copyright 2014 The Gogs Authors. All rights reserved.
2
+// Use of this source code is governed by a MIT-style
3
+// license that can be found in the LICENSE file.
4
+
5
+package user
6
+
7
+import (
8
+	"fmt"
9
+
10
+	"github.com/go-martini/martini"
11
+
12
+	"github.com/gogits/gogs/models"
13
+	"github.com/gogits/gogs/modules/auth"
14
+	"github.com/gogits/gogs/modules/base"
15
+	"github.com/gogits/gogs/modules/middleware"
16
+)
17
+
18
+func Dashboard(ctx *middleware.Context) {
19
+	ctx.Data["Title"] = "Dashboard"
20
+	ctx.Data["PageIsUserDashboard"] = true
21
+	repos, err := models.GetRepositories(&models.User{Id: ctx.User.Id})
22
+	if err != nil {
23
+		ctx.Handle(500, "user.Dashboard", err)
24
+		return
25
+	}
26
+	ctx.Data["MyRepos"] = repos
27
+
28
+	feeds, err := models.GetFeeds(ctx.User.Id, 0, false)
29
+	if err != nil {
30
+		ctx.Handle(500, "user.Dashboard", err)
31
+		return
32
+	}
33
+	ctx.Data["Feeds"] = feeds
34
+	ctx.HTML(200, "user/dashboard")
35
+}
36
+
37
+func Profile(ctx *middleware.Context, params martini.Params) {
38
+	ctx.Data["Title"] = "Profile"
39
+
40
+	// TODO: Need to check view self or others.
41
+	user, err := models.GetUserByName(params["username"])
42
+	if err != nil {
43
+		ctx.Handle(500, "user.Profile", err)
44
+		return
45
+	}
46
+
47
+	ctx.Data["Owner"] = user
48
+
49
+	tab := ctx.Query("tab")
50
+	ctx.Data["TabName"] = tab
51
+
52
+	switch tab {
53
+	case "activity":
54
+		feeds, err := models.GetFeeds(user.Id, 0, true)
55
+		if err != nil {
56
+			ctx.Handle(500, "user.Profile", err)
57
+			return
58
+		}
59
+		ctx.Data["Feeds"] = feeds
60
+	default:
61
+		repos, err := models.GetRepositories(user)
62
+		if err != nil {
63
+			ctx.Handle(500, "user.Profile", err)
64
+			return
65
+		}
66
+		ctx.Data["Repos"] = repos
67
+	}
68
+
69
+	ctx.Data["PageIsUserProfile"] = true
70
+	ctx.HTML(200, "user/profile")
71
+}
72
+
73
+func Email2User(ctx *middleware.Context) {
74
+	u, err := models.GetUserByEmail(ctx.Query("email"))
75
+	if err != nil {
76
+		if err == models.ErrUserNotExist {
77
+			ctx.Handle(404, "user.Email2User", err)
78
+		} else {
79
+			ctx.Handle(500, "user.Email2User(GetUserByEmail)", err)
80
+		}
81
+		return
82
+	}
83
+
84
+	ctx.Redirect("/user/" + u.Name)
85
+}
86
+
87
+const (
88
+	TPL_FEED = `<i class="icon fa fa-%s"></i>
89
+                        <div class="info"><span class="meta">%s</span><br>%s</div>`
90
+)
91
+
92
+func Feeds(ctx *middleware.Context, form auth.FeedsForm) {
93
+	actions, err := models.GetFeeds(form.UserId, form.Page*20, false)
94
+	if err != nil {
95
+		ctx.JSON(500, err)
96
+	}
97
+
98
+	feeds := make([]string, len(actions))
99
+	for i := range actions {
100
+		feeds[i] = fmt.Sprintf(TPL_FEED, base.ActionIcon(actions[i].OpType),
101
+			base.TimeSince(actions[i].Created), base.ActionDesc(actions[i]))
102
+	}
103
+	ctx.JSON(200, &feeds)
104
+}
105
+
106
+func Issues(ctx *middleware.Context) {
107
+	ctx.Data["Title"] = "Your Issues"
108
+	ctx.Data["ViewType"] = "all"
109
+
110
+	page, _ := base.StrTo(ctx.Query("page")).Int()
111
+	repoId, _ := base.StrTo(ctx.Query("repoid")).Int64()
112
+
113
+	ctx.Data["RepoId"] = repoId
114
+
115
+	var posterId int64 = 0
116
+	if ctx.Query("type") == "created_by" {
117
+		posterId = ctx.User.Id
118
+		ctx.Data["ViewType"] = "created_by"
119
+	}
120
+
121
+	// Get all repositories.
122
+	repos, err := models.GetRepositories(ctx.User)
123
+	if err != nil {
124
+		ctx.Handle(200, "user.Issues(get repositories)", err)
125
+		return
126
+	}
127
+
128
+	showRepos := make([]models.Repository, 0, len(repos))
129
+
130
+	isShowClosed := ctx.Query("state") == "closed"
131
+	var closedIssueCount, createdByCount, allIssueCount int
132
+
133
+	// Get all issues.
134
+	allIssues := make([]models.Issue, 0, 5*len(repos))
135
+	for i, repo := range repos {
136
+		issues, err := models.GetIssues(0, repo.Id, posterId, 0, page, isShowClosed, false, "", "")
137
+		if err != nil {
138
+			ctx.Handle(200, "user.Issues(get issues)", err)
139
+			return
140
+		}
141
+
142
+		allIssueCount += repo.NumIssues
143
+		closedIssueCount += repo.NumClosedIssues
144
+
145
+		// Set repository information to issues.
146
+		for j := range issues {
147
+			issues[j].Repo = &repos[i]
148
+		}
149
+		allIssues = append(allIssues, issues...)
150
+
151
+		repos[i].NumOpenIssues = repo.NumIssues - repo.NumClosedIssues
152
+		if repos[i].NumOpenIssues > 0 {
153
+			showRepos = append(showRepos, repos[i])
154
+		}
155
+	}
156
+
157
+	showIssues := make([]models.Issue, 0, len(allIssues))
158
+	ctx.Data["IsShowClosed"] = isShowClosed
159
+
160
+	// Get posters and filter issues.
161
+	for i := range allIssues {
162
+		u, err := models.GetUserById(allIssues[i].PosterId)
163
+		if err != nil {
164
+			ctx.Handle(200, "user.Issues(get poster): %v", err)
165
+			return
166
+		}
167
+		allIssues[i].Poster = u
168
+		if u.Id == ctx.User.Id {
169
+			createdByCount++
170
+		}
171
+
172
+		if repoId > 0 && repoId != allIssues[i].Repo.Id {
173
+			continue
174
+		}
175
+
176
+		if isShowClosed == allIssues[i].IsClosed {
177
+			showIssues = append(showIssues, allIssues[i])
178
+		}
179
+	}
180
+
181
+	ctx.Data["Repos"] = showRepos
182
+	ctx.Data["Issues"] = showIssues
183
+	ctx.Data["AllIssueCount"] = allIssueCount
184
+	ctx.Data["ClosedIssueCount"] = closedIssueCount
185
+	ctx.Data["OpenIssueCount"] = allIssueCount - closedIssueCount
186
+	ctx.Data["CreatedByCount"] = createdByCount
187
+	ctx.HTML(200, "issue/user")
188
+}
189
+
190
+func Pulls(ctx *middleware.Context) {
191
+	ctx.HTML(200, "user/pulls")
192
+}
193
+
194
+func Stars(ctx *middleware.Context) {
195
+	ctx.HTML(200, "user/stars")
196
+}

+ 205 - 1
routers/user/social.go

@@ -7,12 +7,14 @@ package user
7 7
 import (
8 8
 	"encoding/json"
9 9
 	"fmt"
10
+	"net/http"
10 11
 	"net/url"
12
+	"strconv"
11 13
 	"strings"
12 14
 
13 15
 	"code.google.com/p/goauth2/oauth"
14
-
15 16
 	"github.com/go-martini/martini"
17
+
16 18
 	"github.com/gogits/gogs/models"
17 19
 	"github.com/gogits/gogs/modules/base"
18 20
 	"github.com/gogits/gogs/modules/log"
@@ -115,3 +117,205 @@ func SocialSignIn(params martini.Params, ctx *middleware.Context) {
115 117
 	log.Trace("socialId: %v", oa.Id)
116 118
 	ctx.Redirect(next)
117 119
 }
120
+
121
+//   ________.__  __     ___ ___      ___.
122
+//  /  _____/|__|/  |_  /   |   \ __ _\_ |__
123
+// /   \  ___|  \   __\/    ~    \  |  \ __ \
124
+// \    \_\  \  ||  |  \    Y    /  |  / \_\ \
125
+//  \______  /__||__|   \___|_  /|____/|___  /
126
+//         \/                 \/           \/
127
+
128
+type SocialGithub struct {
129
+	Token *oauth.Token
130
+	*oauth.Transport
131
+}
132
+
133
+func (s *SocialGithub) Type() int {
134
+	return models.OT_GITHUB
135
+}
136
+
137
+func init() {
138
+	github := &SocialGithub{}
139
+	name := "github"
140
+	config := &oauth.Config{
141
+		ClientId:     "09383403ff2dc16daaa1",                                       //base.OauthService.GitHub.ClientId, // FIXME: panic when set
142
+		ClientSecret: "0e4aa0c3630df396cdcea01a9d45cacf79925fea",                   //base.OauthService.GitHub.ClientSecret,
143
+		RedirectURL:  strings.TrimSuffix(base.AppUrl, "/") + "/user/login/" + name, //ctx.Req.URL.RequestURI(),
144
+		Scope:        "https://api.github.com/user",
145
+		AuthURL:      "https://github.com/login/oauth/authorize",
146
+		TokenURL:     "https://github.com/login/oauth/access_token",
147
+	}
148
+	github.Transport = &oauth.Transport{
149
+		Config:    config,
150
+		Transport: http.DefaultTransport,
151
+	}
152
+	SocialMap[name] = github
153
+}
154
+
155
+func (s *SocialGithub) SetRedirectUrl(url string) {
156
+	s.Transport.Config.RedirectURL = url
157
+}
158
+
159
+func (s *SocialGithub) UserInfo(token *oauth.Token, _ *url.URL) (*BasicUserInfo, error) {
160
+	transport := &oauth.Transport{
161
+		Token: token,
162
+	}
163
+	var data struct {
164
+		Id    int    `json:"id"`
165
+		Name  string `json:"login"`
166
+		Email string `json:"email"`
167
+	}
168
+	var err error
169
+	r, err := transport.Client().Get(s.Transport.Scope)
170
+	if err != nil {
171
+		return nil, err
172
+	}
173
+	defer r.Body.Close()
174
+	if err = json.NewDecoder(r.Body).Decode(&data); err != nil {
175
+		return nil, err
176
+	}
177
+	return &BasicUserInfo{
178
+		Identity: strconv.Itoa(data.Id),
179
+		Name:     data.Name,
180
+		Email:    data.Email,
181
+	}, nil
182
+}
183
+
184
+//   ________                     .__
185
+//  /  _____/  ____   ____   ____ |  |   ____
186
+// /   \  ___ /  _ \ /  _ \ / ___\|  | _/ __ \
187
+// \    \_\  (  <_> |  <_> ) /_/  >  |_\  ___/
188
+//  \______  /\____/ \____/\___  /|____/\___  >
189
+//         \/             /_____/           \/
190
+
191
+type SocialGoogle struct {
192
+	Token *oauth.Token
193
+	*oauth.Transport
194
+}
195
+
196
+func (s *SocialGoogle) Type() int {
197
+	return models.OT_GOOGLE
198
+}
199
+
200
+func init() {
201
+	google := &SocialGoogle{}
202
+	name := "google"
203
+	// get client id and secret from
204
+	// https://console.developers.google.com/project
205
+	config := &oauth.Config{
206
+		ClientId:     "849753812404-mpd7ilvlb8c7213qn6bre6p6djjskti9.apps.googleusercontent.com", //base.OauthService.GitHub.ClientId, // FIXME: panic when set
207
+		ClientSecret: "VukKc4MwaJUSmiyv3D7ANVCa",                                                 //base.OauthService.GitHub.ClientSecret,
208
+		Scope:        "https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile",
209
+		AuthURL:      "https://accounts.google.com/o/oauth2/auth",
210
+		TokenURL:     "https://accounts.google.com/o/oauth2/token",
211
+	}
212
+	google.Transport = &oauth.Transport{
213
+		Config:    config,
214
+		Transport: http.DefaultTransport,
215
+	}
216
+	SocialMap[name] = google
217
+}
218
+
219
+func (s *SocialGoogle) SetRedirectUrl(url string) {
220
+	s.Transport.Config.RedirectURL = url
221
+}
222
+
223
+func (s *SocialGoogle) UserInfo(token *oauth.Token, _ *url.URL) (*BasicUserInfo, error) {
224
+	transport := &oauth.Transport{Token: token}
225
+	var data struct {
226
+		Id    string `json:"id"`
227
+		Name  string `json:"name"`
228
+		Email string `json:"email"`
229
+	}
230
+	var err error
231
+
232
+	reqUrl := "https://www.googleapis.com/oauth2/v1/userinfo"
233
+	r, err := transport.Client().Get(reqUrl)
234
+	if err != nil {
235
+		return nil, err
236
+	}
237
+	defer r.Body.Close()
238
+	if err = json.NewDecoder(r.Body).Decode(&data); err != nil {
239
+		return nil, err
240
+	}
241
+	return &BasicUserInfo{
242
+		Identity: data.Id,
243
+		Name:     data.Name,
244
+		Email:    data.Email,
245
+	}, nil
246
+}
247
+
248
+// ________   ________
249
+// \_____  \  \_____  \
250
+//  /  / \  \  /  / \  \
251
+// /   \_/.  \/   \_/.  \
252
+// \_____\ \_/\_____\ \_/
253
+//        \__>       \__>
254
+
255
+type SocialQQ struct {
256
+	Token *oauth.Token
257
+	*oauth.Transport
258
+	reqUrl string
259
+}
260
+
261
+func (s *SocialQQ) Type() int {
262
+	return models.OT_QQ
263
+}
264
+
265
+func init() {
266
+	qq := &SocialQQ{}
267
+	name := "qq"
268
+	config := &oauth.Config{
269
+		ClientId:     "801497180",                        //base.OauthService.GitHub.ClientId, // FIXME: panic when set
270
+		ClientSecret: "16cd53b8ad2e16a36fc2c8f87d9388f2", //base.OauthService.GitHub.ClientSecret,
271
+		Scope:        "all",
272
+		AuthURL:      "https://open.t.qq.com/cgi-bin/oauth2/authorize",
273
+		TokenURL:     "https://open.t.qq.com/cgi-bin/oauth2/access_token",
274
+	}
275
+	qq.reqUrl = "https://open.t.qq.com/api/user/info"
276
+	qq.Transport = &oauth.Transport{
277
+		Config:    config,
278
+		Transport: http.DefaultTransport,
279
+	}
280
+	SocialMap[name] = qq
281
+}
282
+
283
+func (s *SocialQQ) SetRedirectUrl(url string) {
284
+	s.Transport.Config.RedirectURL = url
285
+}
286
+
287
+func (s *SocialQQ) UserInfo(token *oauth.Token, URL *url.URL) (*BasicUserInfo, error) {
288
+	var data struct {
289
+		Data struct {
290
+			Id    string `json:"openid"`
291
+			Name  string `json:"name"`
292
+			Email string `json:"email"`
293
+		} `json:"data"`
294
+	}
295
+	var err error
296
+	// https://open.t.qq.com/api/user/info?
297
+	//oauth_consumer_key=APP_KEY&
298
+	//access_token=ACCESSTOKEN&openid=openid
299
+	//clientip=CLIENTIP&oauth_version=2.a
300
+	//scope=all
301
+	var urls = url.Values{
302
+		"oauth_consumer_key": {s.Transport.Config.ClientId},
303
+		"access_token":       {token.AccessToken},
304
+		"openid":             URL.Query()["openid"],
305
+		"oauth_version":      {"2.a"},
306
+		"scope":              {"all"},
307
+	}
308
+	r, err := http.Get(s.reqUrl + "?" + urls.Encode())
309
+	if err != nil {
310
+		return nil, err
311
+	}
312
+	defer r.Body.Close()
313
+	if err = json.NewDecoder(r.Body).Decode(&data); err != nil {
314
+		return nil, err
315
+	}
316
+	return &BasicUserInfo{
317
+		Identity: data.Data.Id,
318
+		Name:     data.Data.Name,
319
+		Email:    data.Data.Email,
320
+	}, nil
321
+}

+ 0 - 73
routers/user/social_github.go

@@ -1,73 +0,0 @@
1
-// Copyright 2014 The Gogs Authors. All rights reserved.
2
-// Use of this source code is governed by a MIT-style
3
-// license that can be found in the LICENSE file.
4
-
5
-package user
6
-
7
-import (
8
-	"encoding/json"
9
-	"net/http"
10
-	"net/url"
11
-	"strconv"
12
-	"strings"
13
-
14
-	"code.google.com/p/goauth2/oauth"
15
-	"github.com/gogits/gogs/models"
16
-	"github.com/gogits/gogs/modules/base"
17
-)
18
-
19
-type SocialGithub struct {
20
-	Token *oauth.Token
21
-	*oauth.Transport
22
-}
23
-
24
-func (s *SocialGithub) Type() int {
25
-	return models.OT_GITHUB
26
-}
27
-
28
-func init() {
29
-	github := &SocialGithub{}
30
-	name := "github"
31
-	config := &oauth.Config{
32
-		ClientId:     "09383403ff2dc16daaa1",                                       //base.OauthService.GitHub.ClientId, // FIXME: panic when set
33
-		ClientSecret: "0e4aa0c3630df396cdcea01a9d45cacf79925fea",                   //base.OauthService.GitHub.ClientSecret,
34
-		RedirectURL:  strings.TrimSuffix(base.AppUrl, "/") + "/user/login/" + name, //ctx.Req.URL.RequestURI(),
35
-		Scope:        "https://api.github.com/user",
36
-		AuthURL:      "https://github.com/login/oauth/authorize",
37
-		TokenURL:     "https://github.com/login/oauth/access_token",
38
-	}
39
-	github.Transport = &oauth.Transport{
40
-		Config:    config,
41
-		Transport: http.DefaultTransport,
42
-	}
43
-	SocialMap[name] = github
44
-}
45
-
46
-func (s *SocialGithub) SetRedirectUrl(url string) {
47
-	s.Transport.Config.RedirectURL = url
48
-}
49
-
50
-func (s *SocialGithub) UserInfo(token *oauth.Token, _ *url.URL) (*BasicUserInfo, error) {
51
-	transport := &oauth.Transport{
52
-		Token: token,
53
-	}
54
-	var data struct {
55
-		Id    int    `json:"id"`
56
-		Name  string `json:"login"`
57
-		Email string `json:"email"`
58
-	}
59
-	var err error
60
-	r, err := transport.Client().Get(s.Transport.Scope)
61
-	if err != nil {
62
-		return nil, err
63
-	}
64
-	defer r.Body.Close()
65
-	if err = json.NewDecoder(r.Body).Decode(&data); err != nil {
66
-		return nil, err
67
-	}
68
-	return &BasicUserInfo{
69
-		Identity: strconv.Itoa(data.Id),
70
-		Name:     data.Name,
71
-		Email:    data.Email,
72
-	}, nil
73
-}

+ 0 - 71
routers/user/social_google.go

@@ -1,71 +0,0 @@
1
-// Copyright 2014 The Gogs Authors. All rights reserved.
2
-// Use of this source code is governed by a MIT-style
3
-// license that can be found in the LICENSE file.
4
-
5
-package user
6
-
7
-import (
8
-	"encoding/json"
9
-	"net/http"
10
-	"net/url"
11
-	"github.com/gogits/gogs/models"
12
-
13
-	"code.google.com/p/goauth2/oauth"
14
-)
15
-
16
-type SocialGoogle struct {
17
-	Token *oauth.Token
18
-	*oauth.Transport
19
-}
20
-
21
-func (s *SocialGoogle) Type() int {
22
-	return models.OT_GOOGLE
23
-}
24
-
25
-func init() {
26
-	google := &SocialGoogle{}
27
-	name := "google"
28
-	// get client id and secret from
29
-	// https://console.developers.google.com/project
30
-	config := &oauth.Config{
31
-		ClientId:     "849753812404-mpd7ilvlb8c7213qn6bre6p6djjskti9.apps.googleusercontent.com", //base.OauthService.GitHub.ClientId, // FIXME: panic when set
32
-		ClientSecret: "VukKc4MwaJUSmiyv3D7ANVCa",                                                 //base.OauthService.GitHub.ClientSecret,
33
-		Scope:        "https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile",
34
-		AuthURL:      "https://accounts.google.com/o/oauth2/auth",
35
-		TokenURL:     "https://accounts.google.com/o/oauth2/token",
36
-	}
37
-	google.Transport = &oauth.Transport{
38
-		Config:    config,
39
-		Transport: http.DefaultTransport,
40
-	}
41
-	SocialMap[name] = google
42
-}
43
-
44
-func (s *SocialGoogle) SetRedirectUrl(url string) {
45
-	s.Transport.Config.RedirectURL = url
46
-}
47
-
48
-func (s *SocialGoogle) UserInfo(token *oauth.Token, _ *url.URL) (*BasicUserInfo, error) {
49
-	transport := &oauth.Transport{Token: token}
50
-	var data struct {
51
-		Id    string `json:"id"`
52
-		Name  string `json:"name"`
53
-		Email string `json:"email"`
54
-	}
55
-	var err error
56
-
57
-	reqUrl := "https://www.googleapis.com/oauth2/v1/userinfo"
58
-	r, err := transport.Client().Get(reqUrl)
59
-	if err != nil {
60
-		return nil, err
61
-	}
62
-	defer r.Body.Close()
63
-	if err = json.NewDecoder(r.Body).Decode(&data); err != nil {
64
-		return nil, err
65
-	}
66
-	return &BasicUserInfo{
67
-		Identity: data.Id,
68
-		Name:     data.Name,
69
-		Email:    data.Email,
70
-	}, nil
71
-}

+ 0 - 83
routers/user/social_qq.go

@@ -1,83 +0,0 @@
1
-// Copyright 2014 The Gogs Authors. All rights reserved.
2
-// Use of this source code is governed by a MIT-style
3
-// license that can be found in the LICENSE file.
4
-
5
-// api reference: http://wiki.open.t.qq.com/index.php/OAuth2.0%E9%89%B4%E6%9D%83/Authorization_code%E6%8E%88%E6%9D%83%E6%A1%88%E4%BE%8B
6
-package user
7
-
8
-import (
9
-	"encoding/json"
10
-	"net/http"
11
-	"net/url"
12
-	"github.com/gogits/gogs/models"
13
-
14
-	"code.google.com/p/goauth2/oauth"
15
-)
16
-
17
-type SocialQQ struct {
18
-	Token *oauth.Token
19
-	*oauth.Transport
20
-	reqUrl string
21
-}
22
-
23
-func (s *SocialQQ) Type() int {
24
-	return models.OT_QQ
25
-}
26
-
27
-func init() {
28
-	qq := &SocialQQ{}
29
-	name := "qq"
30
-	config := &oauth.Config{
31
-		ClientId:     "801497180",                        //base.OauthService.GitHub.ClientId, // FIXME: panic when set
32
-		ClientSecret: "16cd53b8ad2e16a36fc2c8f87d9388f2", //base.OauthService.GitHub.ClientSecret,
33
-		Scope:        "all",
34
-		AuthURL:      "https://open.t.qq.com/cgi-bin/oauth2/authorize",
35
-		TokenURL:     "https://open.t.qq.com/cgi-bin/oauth2/access_token",
36
-	}
37
-	qq.reqUrl = "https://open.t.qq.com/api/user/info"
38
-	qq.Transport = &oauth.Transport{
39
-		Config:    config,
40
-		Transport: http.DefaultTransport,
41
-	}
42
-	SocialMap[name] = qq
43
-}
44
-
45
-func (s *SocialQQ) SetRedirectUrl(url string) {
46
-	s.Transport.Config.RedirectURL = url
47
-}
48
-
49
-func (s *SocialQQ) UserInfo(token *oauth.Token, URL *url.URL) (*BasicUserInfo, error) {
50
-	var data struct {
51
-		Data struct {
52
-			Id    string `json:"openid"`
53
-			Name  string `json:"name"`
54
-			Email string `json:"email"`
55
-		} `json:"data"`
56
-	}
57
-	var err error
58
-	// https://open.t.qq.com/api/user/info?
59
-	//oauth_consumer_key=APP_KEY&
60
-	//access_token=ACCESSTOKEN&openid=openid
61
-	//clientip=CLIENTIP&oauth_version=2.a
62
-	//scope=all
63
-	var urls = url.Values{
64
-		"oauth_consumer_key": {s.Transport.Config.ClientId},
65
-		"access_token":       {token.AccessToken},
66
-		"openid":             URL.Query()["openid"],
67
-		"oauth_version":      {"2.a"},
68
-		"scope":              {"all"},
69
-	}
70
-	r, err := http.Get(s.reqUrl + "?" + urls.Encode())
71
-	if err != nil {
72
-		return nil, err
73
-	}
74
-	defer r.Body.Close()
75
-	if err = json.NewDecoder(r.Body).Decode(&data); err != nil {
76
-		return nil, err
77
-	}
78
-	return &BasicUserInfo{
79
-		Identity: data.Data.Id,
80
-		Name:     data.Data.Name,
81
-		Email:    data.Data.Email,
82
-	}, nil
83
-}

+ 0 - 183
routers/user/user.go

@@ -5,12 +5,9 @@
5 5
 package user
6 6
 
7 7
 import (
8
-	"fmt"
9 8
 	"net/url"
10 9
 	"strings"
11 10
 
12
-	"github.com/go-martini/martini"
13
-
14 11
 	"github.com/gogits/gogs/models"
15 12
 	"github.com/gogits/gogs/modules/auth"
16 13
 	"github.com/gogits/gogs/modules/base"
@@ -19,75 +16,6 @@ import (
19 16
 	"github.com/gogits/gogs/modules/middleware"
20 17
 )
21 18
 
22
-func Dashboard(ctx *middleware.Context) {
23
-	ctx.Data["Title"] = "Dashboard"
24
-	ctx.Data["PageIsUserDashboard"] = true
25
-	repos, err := models.GetRepositories(&models.User{Id: ctx.User.Id})
26
-	if err != nil {
27
-		ctx.Handle(500, "user.Dashboard", err)
28
-		return
29
-	}
30
-	ctx.Data["MyRepos"] = repos
31
-
32
-	feeds, err := models.GetFeeds(ctx.User.Id, 0, false)
33
-	if err != nil {
34
-		ctx.Handle(500, "user.Dashboard", err)
35
-		return
36
-	}
37
-	ctx.Data["Feeds"] = feeds
38
-	ctx.HTML(200, "user/dashboard")
39
-}
40
-
41
-func Profile(ctx *middleware.Context, params martini.Params) {
42
-	ctx.Data["Title"] = "Profile"
43
-
44
-	// TODO: Need to check view self or others.
45
-	user, err := models.GetUserByName(params["username"])
46
-	if err != nil {
47
-		ctx.Handle(500, "user.Profile", err)
48
-		return
49
-	}
50
-
51
-	ctx.Data["Owner"] = user
52
-
53
-	tab := ctx.Query("tab")
54
-	ctx.Data["TabName"] = tab
55
-
56
-	switch tab {
57
-	case "activity":
58
-		feeds, err := models.GetFeeds(user.Id, 0, true)
59
-		if err != nil {
60
-			ctx.Handle(500, "user.Profile", err)
61
-			return
62
-		}
63
-		ctx.Data["Feeds"] = feeds
64
-	default:
65
-		repos, err := models.GetRepositories(user)
66
-		if err != nil {
67
-			ctx.Handle(500, "user.Profile", err)
68
-			return
69
-		}
70
-		ctx.Data["Repos"] = repos
71
-	}
72
-
73
-	ctx.Data["PageIsUserProfile"] = true
74
-	ctx.HTML(200, "user/profile")
75
-}
76
-
77
-func Email2User(ctx *middleware.Context) {
78
-	u, err := models.GetUserByEmail(ctx.Query("email"))
79
-	if err != nil {
80
-		if err == models.ErrUserNotExist {
81
-			ctx.Handle(404, "user.Email2User", err)
82
-		} else {
83
-			ctx.Handle(500, "user.Email2User(GetUserByEmail)", err)
84
-		}
85
-		return
86
-	}
87
-
88
-	ctx.Redirect("/user/" + u.Name)
89
-}
90
-
91 19
 func SignIn(ctx *middleware.Context) {
92 20
 	ctx.Data["Title"] = "Log In"
93 21
 
@@ -329,117 +257,6 @@ func DeletePost(ctx *middleware.Context) {
329 257
 	ctx.Redirect("/user/delete")
330 258
 }
331 259
 
332
-const (
333
-	TPL_FEED = `<i class="icon fa fa-%s"></i>
334
-                        <div class="info"><span class="meta">%s</span><br>%s</div>`
335
-)
336
-
337
-func Feeds(ctx *middleware.Context, form auth.FeedsForm) {
338
-	actions, err := models.GetFeeds(form.UserId, form.Page*20, false)
339
-	if err != nil {
340
-		ctx.JSON(500, err)
341
-	}
342
-
343
-	feeds := make([]string, len(actions))
344
-	for i := range actions {
345
-		feeds[i] = fmt.Sprintf(TPL_FEED, base.ActionIcon(actions[i].OpType),
346
-			base.TimeSince(actions[i].Created), base.ActionDesc(actions[i]))
347
-	}
348
-	ctx.JSON(200, &feeds)
349
-}
350
-
351
-func Issues(ctx *middleware.Context) {
352
-	ctx.Data["Title"] = "Your Issues"
353
-	ctx.Data["ViewType"] = "all"
354
-
355
-	page, _ := base.StrTo(ctx.Query("page")).Int()
356
-	repoId, _ := base.StrTo(ctx.Query("repoid")).Int64()
357
-
358
-	ctx.Data["RepoId"] = repoId
359
-
360
-	var posterId int64 = 0
361
-	if ctx.Query("type") == "created_by" {
362
-		posterId = ctx.User.Id
363
-		ctx.Data["ViewType"] = "created_by"
364
-	}
365
-
366
-	// Get all repositories.
367
-	repos, err := models.GetRepositories(ctx.User)
368
-	if err != nil {
369
-		ctx.Handle(200, "user.Issues(get repositories)", err)
370
-		return
371
-	}
372
-
373
-	showRepos := make([]models.Repository, 0, len(repos))
374
-
375
-	isShowClosed := ctx.Query("state") == "closed"
376
-	var closedIssueCount, createdByCount, allIssueCount int
377
-
378
-	// Get all issues.
379
-	allIssues := make([]models.Issue, 0, 5*len(repos))
380
-	for i, repo := range repos {
381
-		issues, err := models.GetIssues(0, repo.Id, posterId, 0, page, isShowClosed, false, "", "")
382
-		if err != nil {
383
-			ctx.Handle(200, "user.Issues(get issues)", err)
384
-			return
385
-		}
386
-
387
-		allIssueCount += repo.NumIssues
388
-		closedIssueCount += repo.NumClosedIssues
389
-
390
-		// Set repository information to issues.
391
-		for j := range issues {
392
-			issues[j].Repo = &repos[i]
393
-		}
394
-		allIssues = append(allIssues, issues...)
395
-
396
-		repos[i].NumOpenIssues = repo.NumIssues - repo.NumClosedIssues
397
-		if repos[i].NumOpenIssues > 0 {
398
-			showRepos = append(showRepos, repos[i])
399
-		}
400
-	}
401
-
402
-	showIssues := make([]models.Issue, 0, len(allIssues))
403
-	ctx.Data["IsShowClosed"] = isShowClosed
404
-
405
-	// Get posters and filter issues.
406
-	for i := range allIssues {
407
-		u, err := models.GetUserById(allIssues[i].PosterId)
408
-		if err != nil {
409
-			ctx.Handle(200, "user.Issues(get poster): %v", err)
410
-			return
411
-		}
412
-		allIssues[i].Poster = u
413
-		if u.Id == ctx.User.Id {
414
-			createdByCount++
415
-		}
416
-
417
-		if repoId > 0 && repoId != allIssues[i].Repo.Id {
418
-			continue
419
-		}
420
-
421
-		if isShowClosed == allIssues[i].IsClosed {
422
-			showIssues = append(showIssues, allIssues[i])
423
-		}
424
-	}
425
-
426
-	ctx.Data["Repos"] = showRepos
427
-	ctx.Data["Issues"] = showIssues
428
-	ctx.Data["AllIssueCount"] = allIssueCount
429
-	ctx.Data["ClosedIssueCount"] = closedIssueCount
430
-	ctx.Data["OpenIssueCount"] = allIssueCount - closedIssueCount
431
-	ctx.Data["CreatedByCount"] = createdByCount
432
-	ctx.HTML(200, "issue/user")
433
-}
434
-
435
-func Pulls(ctx *middleware.Context) {
436
-	ctx.HTML(200, "user/pulls")
437
-}
438
-
439
-func Stars(ctx *middleware.Context) {
440
-	ctx.HTML(200, "user/stars")
441
-}
442
-
443 260
 func Activate(ctx *middleware.Context) {
444 261
 	code := ctx.Query("code")
445 262
 	if len(code) == 0 {

+ 2 - 3
web.go

@@ -14,7 +14,6 @@ import (
14 14
 
15 15
 	qlog "github.com/qiniu/log"
16 16
 
17
-	"github.com/gogits/binding"
18 17
 	"github.com/gogits/gogs/modules/auth"
19 18
 	"github.com/gogits/gogs/modules/avatar"
20 19
 	"github.com/gogits/gogs/modules/base"
@@ -67,7 +66,7 @@ func runWeb(*cli.Context) {
67 66
 
68 67
 	reqSignOut := middleware.Toggle(&middleware.ToggleOptions{SignOutRequire: true})
69 68
 
70
-	bindIgnErr := binding.BindIgnErr
69
+	bindIgnErr := middleware.BindIgnErr
71 70
 
72 71
 	// Routers.
73 72
 	m.Get("/", ignSignIn, routers.Home)
@@ -102,7 +101,7 @@ func runWeb(*cli.Context) {
102 101
 		r.Post("/setting", bindIgnErr(auth.UpdateProfileForm{}), user.SettingPost)
103 102
 	}, reqSignIn)
104 103
 	m.Group("/user", func(r martini.Router) {
105
-		r.Get("/feeds", binding.Bind(auth.FeedsForm{}), user.Feeds)
104
+		r.Get("/feeds", middleware.Bind(auth.FeedsForm{}), user.Feeds)
106 105
 		r.Get("/activate", user.Activate)
107 106
 		r.Get("/email2user", user.Email2User)
108 107
 		r.Get("/forget_password", user.ForgotPasswd)