Showing 24 of 68 files from the diff.

@@ -55,7 +55,7 @@
Loading
55 55
56 56
// JSON creates a properly formatted JSON
57 57
func (msg *Error) JSON() interface{} {
58 -
	json := H{}
58 +
	jsonData := H{}
59 59
	if msg.Meta != nil {
60 60
		value := reflect.ValueOf(msg.Meta)
61 61
		switch value.Kind() {
@@ -63,16 +63,16 @@
Loading
63 63
			return msg.Meta
64 64
		case reflect.Map:
65 65
			for _, key := range value.MapKeys() {
66 -
				json[key.String()] = value.MapIndex(key).Interface()
66 +
				jsonData[key.String()] = value.MapIndex(key).Interface()
67 67
			}
68 68
		default:
69 -
			json["meta"] = msg.Meta
69 +
			jsonData["meta"] = msg.Meta
70 70
		}
71 71
	}
72 -
	if _, ok := json["error"]; !ok {
73 -
		json["error"] = msg.Error()
72 +
	if _, ok := jsonData["error"]; !ok {
73 +
		jsonData["error"] = msg.Error()
74 74
	}
75 -
	return json
75 +
	return jsonData
76 76
}
77 77
78 78
// MarshalJSON implements the json.Marshaller interface.
@@ -90,6 +90,11 @@
Loading
90 90
	return (msg.Type & flags) > 0
91 91
}
92 92
93 +
// Unwrap returns the wrapped error, to allow interoperability with errors.Is(), errors.As() and errors.Unwrap()
94 +
func (msg *Error) Unwrap() error {
95 +
	return msg.Err
96 +
}
97 +
93 98
// ByType returns a readonly copy filtered the byte.
94 99
// ie ByType(gin.ErrorTypePublic) returns a slice of errors with type=ErrorTypePublic.
95 100
func (a errorMsgs) ByType(typ ErrorType) errorMsgs {
@@ -135,17 +140,17 @@
Loading
135 140
}
136 141
137 142
func (a errorMsgs) JSON() interface{} {
138 -
	switch len(a) {
143 +
	switch length := len(a); length {
139 144
	case 0:
140 145
		return nil
141 146
	case 1:
142 147
		return a.Last().JSON()
143 148
	default:
144 -
		json := make([]interface{}, len(a))
149 +
		jsonData := make([]interface{}, length)
145 150
		for i, err := range a {
146 -
			json[i] = err.JSON()
151 +
			jsonData[i] = err.JSON()
147 152
		}
148 -
		return json
153 +
		return jsonData
149 154
	}
150 155
}
151 156

@@ -8,6 +8,8 @@
Loading
8 8
	"encoding/base64"
9 9
	"net/http"
10 10
	"strconv"
11 +
12 +
	"github.com/gin-gonic/gin/internal/bytesconv"
11 13
)
12 14
13 15
// AuthUserKey is the cookie name for user credential in basic auth.
@@ -68,8 +70,9 @@
Loading
68 70
}
69 71
70 72
func processAccounts(accounts Accounts) authPairs {
71 -
	assert1(len(accounts) > 0, "Empty list of authorized credentials")
72 -
	pairs := make(authPairs, 0, len(accounts))
73 +
	length := len(accounts)
74 +
	assert1(length > 0, "Empty list of authorized credentials")
75 +
	pairs := make(authPairs, 0, length)
73 76
	for user, password := range accounts {
74 77
		assert1(user != "", "User can not be empty")
75 78
		value := authorizationHeader(user, password)
@@ -83,5 +86,5 @@
Loading
83 86
84 87
func authorizationHeader(user, password string) string {
85 88
	base := user + ":" + password
86 -
	return "Basic " + base64.StdEncoding.EncodeToString([]byte(base))
89 +
	return "Basic " + base64.StdEncoding.EncodeToString(bytesconv.StringToBytes(base))
87 90
}

@@ -8,7 +8,7 @@
Loading
8 8
	"net/http"
9 9
)
10 10
11 -
const defaultMemory = 32 * 1024 * 1024
11 +
const defaultMemory = 32 << 20
12 12
13 13
type formBinding struct{}
14 14
type formPostBinding struct{}

@@ -13,24 +13,26 @@
Loading
13 13
	"path"
14 14
	"sync"
15 15
16 +
	"github.com/gin-gonic/gin/internal/bytesconv"
16 17
	"github.com/gin-gonic/gin/render"
17 18
)
18 19
19 20
const defaultMultipartMemory = 32 << 20 // 32 MB
20 21
21 22
var (
22 -
	default404Body   = []byte("404 page not found")
23 -
	default405Body   = []byte("405 method not allowed")
24 -
	defaultAppEngine bool
23 +
	default404Body = []byte("404 page not found")
24 +
	default405Body = []byte("405 method not allowed")
25 25
)
26 26
27 +
var defaultAppEngine bool
28 +
27 29
// HandlerFunc defines the handler used by gin middleware as return value.
28 30
type HandlerFunc func(*Context)
29 31
30 32
// HandlersChain defines a HandlerFunc array.
31 33
type HandlersChain []HandlerFunc
32 34
33 -
// Last returns the last handler in the chain. ie. the last handler is the main own.
35 +
// Last returns the last handler in the chain. ie. the last handler is the main one.
34 36
func (c HandlersChain) Last() HandlerFunc {
35 37
	if length := len(c); length > 0 {
36 38
		return c[length-1]
@@ -97,8 +99,12 @@
Loading
97 99
	// method call.
98 100
	MaxMultipartMemory int64
99 101
102 +
	// RemoveExtraSlash a parameter can be parsed from the URL even with extra slashes.
103 +
	// See the PR #1817 and issue #1644
104 +
	RemoveExtraSlash bool
105 +
100 106
	delims           render.Delims
101 -
	secureJsonPrefix string
107 +
	secureJSONPrefix string
102 108
	HTMLRender       render.HTMLRender
103 109
	FuncMap          template.FuncMap
104 110
	allNoRoute       HandlersChain
@@ -107,6 +113,7 @@
Loading
107 113
	noMethod         HandlersChain
108 114
	pool             sync.Pool
109 115
	trees            methodTrees
116 +
	maxParams        uint16
110 117
}
111 118
112 119
var _ IRouter = &Engine{}
@@ -134,11 +141,12 @@
Loading
134 141
		ForwardedByClientIP:    true,
135 142
		AppEngine:              defaultAppEngine,
136 143
		UseRawPath:             false,
144 +
		RemoveExtraSlash:       false,
137 145
		UnescapePathValues:     true,
138 146
		MaxMultipartMemory:     defaultMultipartMemory,
139 147
		trees:                  make(methodTrees, 0, 9),
140 148
		delims:                 render.Delims{Left: "{{", Right: "}}"},
141 -
		secureJsonPrefix:       "while(1);",
149 +
		secureJSONPrefix:       "while(1);",
142 150
	}
143 151
	engine.RouterGroup.engine = engine
144 152
	engine.pool.New = func() interface{} {
@@ -156,7 +164,8 @@
Loading
156 164
}
157 165
158 166
func (engine *Engine) allocateContext() *Context {
159 -
	return &Context{engine: engine}
167 +
	v := make(Params, 0, engine.maxParams)
168 +
	return &Context{engine: engine, params: &v}
160 169
}
161 170
162 171
// Delims sets template left and right delims and returns a Engine instance.
@@ -165,9 +174,9 @@
Loading
165 174
	return engine
166 175
}
167 176
168 -
// SecureJsonPrefix sets the secureJsonPrefix used in Context.SecureJSON.
177 +
// SecureJsonPrefix sets the secureJSONPrefix used in Context.SecureJSON.
169 178
func (engine *Engine) SecureJsonPrefix(prefix string) *Engine {
170 -
	engine.secureJsonPrefix = prefix
179 +
	engine.secureJSONPrefix = prefix
171 180
	return engine
172 181
}
173 182
@@ -249,6 +258,7 @@
Loading
249 258
	assert1(len(handlers) > 0, "there must be at least one handler")
250 259
251 260
	debugPrintRoute(method, path, handlers)
261 +
252 262
	root := engine.trees.get(method)
253 263
	if root == nil {
254 264
		root = new(node)
@@ -256,6 +266,11 @@
Loading
256 266
		engine.trees = append(engine.trees, methodTree{method: method, root: root})
257 267
	}
258 268
	root.addRoute(path, handlers)
269 +
270 +
	// Update maxParams
271 +
	if paramsCount := countParams(path); paramsCount > engine.maxParams {
272 +
		engine.maxParams = paramsCount
273 +
	}
259 274
}
260 275
261 276
// Routes returns a slice of registered routes, including some useful information, such as:
@@ -314,13 +329,13 @@
Loading
314 329
	debugPrint("Listening and serving HTTP on unix:/%s", file)
315 330
	defer func() { debugPrintError(err) }()
316 331
317 -
	os.Remove(file)
318 332
	listener, err := net.Listen("unix", file)
319 333
	if err != nil {
320 334
		return
321 335
	}
322 336
	defer listener.Close()
323 -
	os.Chmod(file, 0777)
337 +
	defer os.Remove(file)
338 +
324 339
	err = http.Serve(listener, engine)
325 340
	return
326 341
}
@@ -338,6 +353,15 @@
Loading
338 353
		return
339 354
	}
340 355
	defer listener.Close()
356 +
	err = engine.RunListener(listener)
357 +
	return
358 +
}
359 +
360 +
// RunListener attaches the router to a http.Server and starts listening and serving HTTP requests
361 +
// through the specified net.Listener
362 +
func (engine *Engine) RunListener(listener net.Listener) (err error) {
363 +
	debugPrint("Listening and serving HTTP on listener what's bind with address@%s", listener.Addr())
364 +
	defer func() { debugPrintError(err) }()
341 365
	err = http.Serve(listener, engine)
342 366
	return
343 367
}
@@ -373,7 +397,10 @@
Loading
373 397
		rPath = c.Request.URL.RawPath
374 398
		unescape = engine.UnescapePathValues
375 399
	}
376 -
	rPath = cleanPath(rPath)
400 +
401 +
	if engine.RemoveExtraSlash {
402 +
		rPath = cleanPath(rPath)
403 +
	}
377 404
378 405
	// Find root of the tree for the given HTTP method
379 406
	t := engine.trees
@@ -383,10 +410,12 @@
Loading
383 410
		}
384 411
		root := t[i].root
385 412
		// Find route in tree
386 -
		value := root.getValue(rPath, c.Params, unescape)
413 +
		value := root.getValue(rPath, c.params, unescape)
414 +
		if value.params != nil {
415 +
			c.Params = *value.params
416 +
		}
387 417
		if value.handlers != nil {
388 418
			c.handlers = value.handlers
389 -
			c.Params = value.params
390 419
			c.fullPath = value.fullPath
391 420
			c.Next()
392 421
			c.writermem.WriteHeaderNow()
@@ -445,18 +474,11 @@
Loading
445 474
	if prefix := path.Clean(c.Request.Header.Get("X-Forwarded-Prefix")); prefix != "." {
446 475
		p = prefix + "/" + req.URL.Path
447 476
	}
448 -
	code := http.StatusMovedPermanently // Permanent redirect, request with GET method
449 -
	if req.Method != "GET" {
450 -
		code = http.StatusTemporaryRedirect
451 -
	}
452 -
453 477
	req.URL.Path = p + "/"
454 478
	if length := len(p); length > 1 && p[length-1] == '/' {
455 479
		req.URL.Path = p[:length-1]
456 480
	}
457 -
	debugPrint("redirecting request %d: %s --> %s", code, p, req.URL.String())
458 -
	http.Redirect(c.Writer, req, req.URL.String(), code)
459 -
	c.writermem.WriteHeaderNow()
481 +
	redirectRequest(c)
460 482
}
461 483
462 484
func redirectFixedPath(c *Context, root *node, trailingSlash bool) bool {
@@ -464,15 +486,23 @@
Loading
464 486
	rPath := req.URL.Path
465 487
466 488
	if fixedPath, ok := root.findCaseInsensitivePath(cleanPath(rPath), trailingSlash); ok {
467 -
		code := http.StatusMovedPermanently // Permanent redirect, request with GET method
468 -
		if req.Method != "GET" {
469 -
			code = http.StatusTemporaryRedirect
470 -
		}
471 -
		req.URL.Path = string(fixedPath)
472 -
		debugPrint("redirecting request %d: %s --> %s", code, rPath, req.URL.String())
473 -
		http.Redirect(c.Writer, req, req.URL.String(), code)
474 -
		c.writermem.WriteHeaderNow()
489 +
		req.URL.Path = bytesconv.BytesToString(fixedPath)
490 +
		redirectRequest(c)
475 491
		return true
476 492
	}
477 493
	return false
478 494
}
495 +
496 +
func redirectRequest(c *Context) {
497 +
	req := c.Request
498 +
	rPath := req.URL.Path
499 +
	rURL := req.URL.String()
500 +
501 +
	code := http.StatusMovedPermanently // Permanent redirect, request with GET method
502 +
	if req.Method != http.MethodGet {
503 +
		code = http.StatusTemporaryRedirect
504 +
	}
505 +
	debugPrint("redirecting request %d: %s --> %s", code, rPath, rURL)
506 +
	http.Redirect(c.Writer, req, rURL, code)
507 +
	c.writermem.WriteHeaderNow()
508 +
}

@@ -67,7 +67,7 @@
Loading
67 67
68 68
func debugPrintWARNINGDefault() {
69 69
	if v, e := getMinVer(runtime.Version()); e == nil && v <= ginSupportMinGoVer {
70 -
		debugPrint(`[WARNING] Now Gin requires Go 1.10 or later and Go 1.11 will be required soon.
70 +
		debugPrint(`[WARNING] Now Gin requires Go 1.11 or later and Go 1.12 will be required soon.
71 71
72 72
`)
73 73
	}

@@ -90,20 +90,23 @@
Loading
90 90
}
91 91
92 92
func chooseData(custom, wildcard interface{}) interface{} {
93 -
	if custom == nil {
94 -
		if wildcard == nil {
95 -
			panic("negotiation config is invalid")
96 -
		}
93 +
	if custom != nil {
94 +
		return custom
95 +
	}
96 +
	if wildcard != nil {
97 97
		return wildcard
98 98
	}
99 -
	return custom
99 +
	panic("negotiation config is invalid")
100 100
}
101 101
102 102
func parseAccept(acceptHeader string) []string {
103 103
	parts := strings.Split(acceptHeader, ",")
104 104
	out := make([]string, 0, len(parts))
105 105
	for _, part := range parts {
106 -
		if part = strings.TrimSpace(strings.Split(part, ";")[0]); part != "" {
106 +
		if i := strings.IndexByte(part, ';'); i > 0 {
107 +
			part = part[:i]
108 +
		}
109 +
		if part = strings.TrimSpace(part); part != "" {
107 110
			out = append(out, part)
108 111
		}
109 112
	}
@@ -127,8 +130,7 @@
Loading
127 130
	}
128 131
129 132
	finalPath := path.Join(absolutePath, relativePath)
130 -
	appendSlash := lastChar(relativePath) == '/' && lastChar(finalPath) != '/'
131 -
	if appendSlash {
133 +
	if lastChar(relativePath) == '/' && lastChar(finalPath) != '/' {
132 134
		return finalPath + "/"
133 135
	}
134 136
	return finalPath

@@ -12,10 +12,11 @@
Loading
12 12
	"strings"
13 13
	"time"
14 14
15 +
	"github.com/gin-gonic/gin/internal/bytesconv"
15 16
	"github.com/gin-gonic/gin/internal/json"
16 17
)
17 18
18 -
var errUnknownType = errors.New("Unknown type")
19 +
var errUnknownType = errors.New("unknown type")
19 20
20 21
func mapUri(ptr interface{}, m map[string][]string) error {
21 22
	return mapFormByTag(ptr, m, "uri")
@@ -51,6 +52,10 @@
Loading
51 52
}
52 53
53 54
func mapping(value reflect.Value, field reflect.StructField, setter setter, tag string) (bool, error) {
55 +
	if field.Tag.Get(tag) == "-" { // just ignoring this field
56 +
		return false, nil
57 +
	}
58 +
54 59
	var vKind = value.Kind()
55 60
56 61
	if vKind == reflect.Ptr {
@@ -112,9 +117,6 @@
Loading
112 117
	tagValue = field.Tag.Get(tag)
113 118
	tagValue, opts := head(tagValue, ",")
114 119
115 -
	if tagValue == "-" { // just ignoring this field
116 -
		return false, nil
117 -
	}
118 120
	if tagValue == "" { // default value is FieldName
119 121
		tagValue = field.Name
120 122
	}
@@ -207,9 +209,9 @@
Loading
207 209
		case time.Time:
208 210
			return setTimeField(val, field, value)
209 211
		}
210 -
		return json.Unmarshal([]byte(val), value.Addr().Interface())
212 +
		return json.Unmarshal(bytesconv.StringToBytes(val), value.Addr().Interface())
211 213
	case reflect.Map:
212 -
		return json.Unmarshal([]byte(val), value.Addr().Interface())
214 +
		return json.Unmarshal(bytesconv.StringToBytes(val), value.Addr().Interface())
213 215
	default:
214 216
		return errUnknownType
215 217
	}
@@ -268,7 +270,7 @@
Loading
268 270
269 271
	switch tf := strings.ToLower(timeFormat); tf {
270 272
	case "unix", "unixnano":
271 -
		tv, err := strconv.ParseInt(val, 10, 0)
273 +
		tv, err := strconv.ParseInt(val, 10, 64)
272 274
		if err != nil {
273 275
			return err
274 276
		}

@@ -99,19 +99,19 @@
Loading
99 99
	method := p.Method
100 100
101 101
	switch method {
102 -
	case "GET":
102 +
	case http.MethodGet:
103 103
		return blue
104 -
	case "POST":
104 +
	case http.MethodPost:
105 105
		return cyan
106 -
	case "PUT":
106 +
	case http.MethodPut:
107 107
		return yellow
108 -
	case "DELETE":
108 +
	case http.MethodDelete:
109 109
		return red
110 -
	case "PATCH":
110 +
	case http.MethodPatch:
111 111
		return green
112 -
	case "HEAD":
112 +
	case http.MethodHead:
113 113
		return magenta
114 -
	case "OPTIONS":
114 +
	case http.MethodOptions:
115 115
		return white
116 116
	default:
117 117
		return reset
@@ -141,7 +141,7 @@
Loading
141 141
		// Truncate in a golang < 1.8 safe way
142 142
		param.Latency = param.Latency - param.Latency%time.Second
143 143
	}
144 -
	return fmt.Sprintf("[GIN] %v |%s %3d %s| %13v | %15s |%s %-7s %s %s\n%s",
144 +
	return fmt.Sprintf("[GIN] %v |%s %3d %s| %13v | %15s |%s %-7s %s %#v\n%s",
145 145
		param.TimeStamp.Format("2006/01/02 - 15:04:05"),
146 146
		statusColor, param.StatusCode, resetColor,
147 147
		param.Latency,

@@ -26,13 +26,29 @@
Loading
26 26
	slash     = []byte("/")
27 27
)
28 28
29 +
// RecoveryFunc defines the function passable to CustomRecovery.
30 +
type RecoveryFunc func(c *Context, err interface{})
31 +
29 32
// Recovery returns a middleware that recovers from any panics and writes a 500 if there was one.
30 33
func Recovery() HandlerFunc {
31 34
	return RecoveryWithWriter(DefaultErrorWriter)
32 35
}
33 36
37 +
//CustomRecovery returns a middleware that recovers from any panics and calls the provided handle func to handle it.
38 +
func CustomRecovery(handle RecoveryFunc) HandlerFunc {
39 +
	return RecoveryWithWriter(DefaultErrorWriter, handle)
40 +
}
41 +
34 42
// RecoveryWithWriter returns a middleware for a given writer that recovers from any panics and writes a 500 if there was one.
35 -
func RecoveryWithWriter(out io.Writer) HandlerFunc {
43 +
func RecoveryWithWriter(out io.Writer, recovery ...RecoveryFunc) HandlerFunc {
44 +
	if len(recovery) > 0 {
45 +
		return CustomRecoveryWithWriter(out, recovery[0])
46 +
	}
47 +
	return CustomRecoveryWithWriter(out, defaultHandleRecovery)
48 +
}
49 +
50 +
// CustomRecoveryWithWriter returns a middleware for a given writer that recovers from any panics and calls the provided handle func to handle it.
51 +
func CustomRecoveryWithWriter(out io.Writer, handle RecoveryFunc) HandlerFunc {
36 52
	var logger *log.Logger
37 53
	if out != nil {
38 54
		logger = log.New(out, "\n\n\x1b[31m", log.LstdFlags)
@@ -60,23 +76,23 @@
Loading
60 76
							headers[idx] = current[0] + ": *"
61 77
						}
62 78
					}
79 +
					headersToStr := strings.Join(headers, "\r\n")
63 80
					if brokenPipe {
64 -
						logger.Printf("%s\n%s%s", err, string(httpRequest), reset)
81 +
						logger.Printf("%s\n%s%s", err, headersToStr, reset)
65 82
					} else if IsDebugging() {
66 83
						logger.Printf("[Recovery] %s panic recovered:\n%s\n%s\n%s%s",
67 -
							timeFormat(time.Now()), strings.Join(headers, "\r\n"), err, stack, reset)
84 +
							timeFormat(time.Now()), headersToStr, err, stack, reset)
68 85
					} else {
69 86
						logger.Printf("[Recovery] %s panic recovered:\n%s\n%s%s",
70 87
							timeFormat(time.Now()), err, stack, reset)
71 88
					}
72 89
				}
73 -
74 -
				// If the connection is dead, we can't write a status to it.
75 90
				if brokenPipe {
91 +
					// If the connection is dead, we can't write a status to it.
76 92
					c.Error(err.(error)) // nolint: errcheck
77 93
					c.Abort()
78 94
				} else {
79 -
					c.AbortWithStatus(http.StatusInternalServerError)
95 +
					handle(c, err)
80 96
				}
81 97
			}
82 98
		}()
@@ -84,6 +100,10 @@
Loading
84 100
	}
85 101
}
86 102
103 +
func defaultHandleRecovery(c *Context, err interface{}) {
104 +
	c.AbortWithStatus(http.StatusInternalServerError)
105 +
}
106 +
87 107
// stack returns a nicely formatted stack frame, skipping skip frames.
88 108
func stack(skip int) []byte {
89 109
	buf := new(bytes.Buffer) // the returned data
@@ -146,6 +166,6 @@
Loading
146 166
}
147 167
148 168
func timeFormat(t time.Time) string {
149 -
	var timeString = t.Format("2006/01/02 - 15:04:05")
169 +
	timeString := t.Format("2006/01/02 - 15:04:05")
150 170
	return timeString
151 171
}

@@ -10,6 +10,7 @@
Loading
10 10
	"html/template"
11 11
	"net/http"
12 12
13 +
	"github.com/gin-gonic/gin/internal/bytesconv"
13 14
	"github.com/gin-gonic/gin/internal/json"
14 15
)
15 16
@@ -40,9 +41,6 @@
Loading
40 41
	Data interface{}
41 42
}
42 43
43 -
// SecureJSONPrefix is a string which represents SecureJSON prefix.
44 -
type SecureJSONPrefix string
45 -
46 44
// PureJSON contains the given interface object.
47 45
type PureJSON struct {
48 46
	Data interface{}
@@ -68,8 +66,11 @@
Loading
68 66
// WriteJSON marshals the given interface object and writes it with custom ContentType.
69 67
func WriteJSON(w http.ResponseWriter, obj interface{}) error {
70 68
	writeContentType(w, jsonContentType)
71 -
	encoder := json.NewEncoder(w)
72 -
	err := encoder.Encode(&obj)
69 +
	jsonBytes, err := json.Marshal(obj)
70 +
	if err != nil {
71 +
		return err
72 +
	}
73 +
	_, err = w.Write(jsonBytes)
73 74
	return err
74 75
}
75 76
@@ -97,8 +98,9 @@
Loading
97 98
		return err
98 99
	}
99 100
	// if the jsonBytes is array values
100 -
	if bytes.HasPrefix(jsonBytes, []byte("[")) && bytes.HasSuffix(jsonBytes, []byte("]")) {
101 -
		_, err = w.Write([]byte(r.Prefix))
101 +
	if bytes.HasPrefix(jsonBytes, bytesconv.StringToBytes("[")) && bytes.HasSuffix(jsonBytes,
102 +
		bytesconv.StringToBytes("]")) {
103 +
		_, err = w.Write(bytesconv.StringToBytes(r.Prefix))
102 104
		if err != nil {
103 105
			return err
104 106
		}
@@ -126,11 +128,11 @@
Loading
126 128
	}
127 129
128 130
	callback := template.JSEscapeString(r.Callback)
129 -
	_, err = w.Write([]byte(callback))
131 +
	_, err = w.Write(bytesconv.StringToBytes(callback))
130 132
	if err != nil {
131 133
		return err
132 134
	}
133 -
	_, err = w.Write([]byte("("))
135 +
	_, err = w.Write(bytesconv.StringToBytes("("))
134 136
	if err != nil {
135 137
		return err
136 138
	}
@@ -138,7 +140,7 @@
Loading
138 140
	if err != nil {
139 141
		return err
140 142
	}
141 -
	_, err = w.Write([]byte(");"))
143 +
	_, err = w.Write(bytesconv.StringToBytes(");"))
142 144
	if err != nil {
143 145
		return err
144 146
	}
@@ -160,7 +162,7 @@
Loading
160 162
	}
161 163
162 164
	var buffer bytes.Buffer
163 -
	for _, r := range string(ret) {
165 +
	for _, r := range bytesconv.BytesToString(ret) {
164 166
		cvt := string(r)
165 167
		if r >= 128 {
166 168
			cvt = fmt.Sprintf("\\u%04x", int64(r))

@@ -9,7 +9,7 @@
Loading
9 9
	"os"
10 10
)
11 11
12 -
type onlyfilesFS struct {
12 +
type onlyFilesFS struct {
13 13
	fs http.FileSystem
14 14
}
15 15
@@ -26,11 +26,11 @@
Loading
26 26
	if listDirectory {
27 27
		return fs
28 28
	}
29 -
	return &onlyfilesFS{fs}
29 +
	return &onlyFilesFS{fs}
30 30
}
31 31
32 32
// Open conforms to http.Filesystem.
33 -
func (fs onlyfilesFS) Open(name string) (http.File, error) {
33 +
func (fs onlyFilesFS) Open(name string) (http.File, error) {
34 34
	f, err := fs.fs.Open(name)
35 35
	if err != nil {
36 36
		return nil, err

@@ -95,51 +95,51 @@
Loading
95 95
96 96
// POST is a shortcut for router.Handle("POST", path, handle).
97 97
func (group *RouterGroup) POST(relativePath string, handlers ...HandlerFunc) IRoutes {
98 -
	return group.handle("POST", relativePath, handlers)
98 +
	return group.handle(http.MethodPost, relativePath, handlers)
99 99
}
100 100
101 101
// GET is a shortcut for router.Handle("GET", path, handle).
102 102
func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {
103 -
	return group.handle("GET", relativePath, handlers)
103 +
	return group.handle(http.MethodGet, relativePath, handlers)
104 104
}
105 105
106 106
// DELETE is a shortcut for router.Handle("DELETE", path, handle).
107 107
func (group *RouterGroup) DELETE(relativePath string, handlers ...HandlerFunc) IRoutes {
108 -
	return group.handle("DELETE", relativePath, handlers)
108 +
	return group.handle(http.MethodDelete, relativePath, handlers)
109 109
}
110 110
111 111
// PATCH is a shortcut for router.Handle("PATCH", path, handle).
112 112
func (group *RouterGroup) PATCH(relativePath string, handlers ...HandlerFunc) IRoutes {
113 -
	return group.handle("PATCH", relativePath, handlers)
113 +
	return group.handle(http.MethodPatch, relativePath, handlers)
114 114
}
115 115
116 116
// PUT is a shortcut for router.Handle("PUT", path, handle).
117 117
func (group *RouterGroup) PUT(relativePath string, handlers ...HandlerFunc) IRoutes {
118 -
	return group.handle("PUT", relativePath, handlers)
118 +
	return group.handle(http.MethodPut, relativePath, handlers)
119 119
}
120 120
121 121
// OPTIONS is a shortcut for router.Handle("OPTIONS", path, handle).
122 122
func (group *RouterGroup) OPTIONS(relativePath string, handlers ...HandlerFunc) IRoutes {
123 -
	return group.handle("OPTIONS", relativePath, handlers)
123 +
	return group.handle(http.MethodOptions, relativePath, handlers)
124 124
}
125 125
126 126
// HEAD is a shortcut for router.Handle("HEAD", path, handle).
127 127
func (group *RouterGroup) HEAD(relativePath string, handlers ...HandlerFunc) IRoutes {
128 -
	return group.handle("HEAD", relativePath, handlers)
128 +
	return group.handle(http.MethodHead, relativePath, handlers)
129 129
}
130 130
131 131
// Any registers a route that matches all the HTTP methods.
132 132
// GET, POST, PUT, PATCH, HEAD, OPTIONS, DELETE, CONNECT, TRACE.
133 133
func (group *RouterGroup) Any(relativePath string, handlers ...HandlerFunc) IRoutes {
134 -
	group.handle("GET", relativePath, handlers)
135 -
	group.handle("POST", relativePath, handlers)
136 -
	group.handle("PUT", relativePath, handlers)
137 -
	group.handle("PATCH", relativePath, handlers)
138 -
	group.handle("HEAD", relativePath, handlers)
139 -
	group.handle("OPTIONS", relativePath, handlers)
140 -
	group.handle("DELETE", relativePath, handlers)
141 -
	group.handle("CONNECT", relativePath, handlers)
142 -
	group.handle("TRACE", relativePath, handlers)
134 +
	group.handle(http.MethodGet, relativePath, handlers)
135 +
	group.handle(http.MethodPost, relativePath, handlers)
136 +
	group.handle(http.MethodPut, relativePath, handlers)
137 +
	group.handle(http.MethodPatch, relativePath, handlers)
138 +
	group.handle(http.MethodHead, relativePath, handlers)
139 +
	group.handle(http.MethodOptions, relativePath, handlers)
140 +
	group.handle(http.MethodDelete, relativePath, handlers)
141 +
	group.handle(http.MethodConnect, relativePath, handlers)
142 +
	group.handle(http.MethodTrace, relativePath, handlers)
143 143
	return group.returnObj()
144 144
}
145 145
@@ -187,19 +187,21 @@
Loading
187 187
	fileServer := http.StripPrefix(absolutePath, http.FileServer(fs))
188 188
189 189
	return func(c *Context) {
190 -
		if _, nolisting := fs.(*onlyfilesFS); nolisting {
190 +
		if _, noListing := fs.(*onlyFilesFS); noListing {
191 191
			c.Writer.WriteHeader(http.StatusNotFound)
192 192
		}
193 193
194 194
		file := c.Param("filepath")
195 195
		// Check if file exists and/or if we have permission to access it
196 -
		if _, err := fs.Open(file); err != nil {
196 +
		f, err := fs.Open(file)
197 +
		if err != nil {
197 198
			c.Writer.WriteHeader(http.StatusNotFound)
198 199
			c.handlers = group.engine.noRoute
199 200
			// Reset index
200 201
			c.index = -1
201 202
			return
202 203
		}
204 +
		f.Close()
203 205
204 206
		fileServer.ServeHTTP(c.Writer, c.Request)
205 207
	}

@@ -5,9 +5,18 @@
Loading
5 5
package gin
6 6
7 7
import (
8 +
	"bytes"
8 9
	"net/url"
9 10
	"strings"
10 11
	"unicode"
12 +
	"unicode/utf8"
13 +
14 +
	"github.com/gin-gonic/gin/internal/bytesconv"
15 +
)
16 +
17 +
var (
18 +
	strColon = []byte(":")
19 +
	strStar  = []byte("*")
11 20
)
12 21
13 22
// Param is a single URL parameter, consisting of a key and a value.
@@ -62,18 +71,21 @@
Loading
62 71
	return b
63 72
}
64 73
65 -
func countParams(path string) uint8 {
66 -
	var n uint
67 -
	for i := 0; i < len(path); i++ {
68 -
		if path[i] != ':' && path[i] != '*' {
69 -
			continue
70 -
		}
71 -
		n++
74 +
func longestCommonPrefix(a, b string) int {
75 +
	i := 0
76 +
	max := min(len(a), len(b))
77 +
	for i < max && a[i] == b[i] {
78 +
		i++
72 79
	}
73 -
	if n >= 255 {
74 -
		return 255
75 -
	}
76 -
	return uint8(n)
80 +
	return i
81 +
}
82 +
83 +
func countParams(path string) uint16 {
84 +
	var n uint16
85 +
	s := bytesconv.StringToBytes(path)
86 +
	n += uint16(bytes.Count(s, strColon))
87 +
	n += uint16(bytes.Count(s, strStar))
88 +
	return n
77 89
}
78 90
79 91
type nodeType uint8
@@ -88,34 +100,33 @@
Loading
88 100
type node struct {
89 101
	path      string
90 102
	indices   string
103 +
	wildChild bool
104 +
	nType     nodeType
105 +
	priority  uint32
91 106
	children  []*node
92 107
	handlers  HandlersChain
93 -
	priority  uint32
94 -
	nType     nodeType
95 -
	maxParams uint8
96 -
	wildChild bool
97 108
	fullPath  string
98 109
}
99 110
100 -
// increments priority of the given child and reorders if necessary.
111 +
// Increments priority of the given child and reorders if necessary
101 112
func (n *node) incrementChildPrio(pos int) int {
102 -
	n.children[pos].priority++
103 -
	prio := n.children[pos].priority
113 +
	cs := n.children
114 +
	cs[pos].priority++
115 +
	prio := cs[pos].priority
104 116
105 -
	// adjust position (move to front)
117 +
	// Adjust position (move to front)
106 118
	newPos := pos
107 -
	for newPos > 0 && n.children[newPos-1].priority < prio {
108 -
		// swap node positions
109 -
		n.children[newPos-1], n.children[newPos] = n.children[newPos], n.children[newPos-1]
119 +
	for ; newPos > 0 && cs[newPos-1].priority < prio; newPos-- {
120 +
		// Swap node positions
121 +
		cs[newPos-1], cs[newPos] = cs[newPos], cs[newPos-1]
110 122
111 -
		newPos--
112 123
	}
113 124
114 -
	// build new index char string
125 +
	// Build new index char string
115 126
	if newPos != pos {
116 -
		n.indices = n.indices[:newPos] + // unchanged prefix, might be empty
117 -
			n.indices[pos:pos+1] + // the index char we move
118 -
			n.indices[newPos:pos] + n.indices[pos+1:] // rest without char at 'pos'
127 +
		n.indices = n.indices[:newPos] + // Unchanged prefix, might be empty
128 +
			n.indices[pos:pos+1] + // The index char we move
129 +
			n.indices[newPos:pos] + n.indices[pos+1:] // Rest without char at 'pos'
119 130
	}
120 131
121 132
	return newPos
@@ -126,254 +137,250 @@
Loading
126 137
func (n *node) addRoute(path string, handlers HandlersChain) {
127 138
	fullPath := path
128 139
	n.priority++
129 -
	numParams := countParams(path)
140 +
141 +
	// Empty tree
142 +
	if len(n.path) == 0 && len(n.children) == 0 {
143 +
		n.insertChild(path, fullPath, handlers)
144 +
		n.nType = root
145 +
		return
146 +
	}
130 147
131 148
	parentFullPathIndex := 0
132 149
133 -
	// non-empty tree
134 -
	if len(n.path) > 0 || len(n.children) > 0 {
135 -
	walk:
136 -
		for {
137 -
			// Update maxParams of the current node
138 -
			if numParams > n.maxParams {
139 -
				n.maxParams = numParams
150 +
walk:
151 +
	for {
152 +
		// Find the longest common prefix.
153 +
		// This also implies that the common prefix contains no ':' or '*'
154 +
		// since the existing key can't contain those chars.
155 +
		i := longestCommonPrefix(path, n.path)
156 +
157 +
		// Split edge
158 +
		if i < len(n.path) {
159 +
			child := node{
160 +
				path:      n.path[i:],
161 +
				wildChild: n.wildChild,
162 +
				indices:   n.indices,
163 +
				children:  n.children,
164 +
				handlers:  n.handlers,
165 +
				priority:  n.priority - 1,
166 +
				fullPath:  n.fullPath,
140 167
			}
141 168
142 -
			// Find the longest common prefix.
143 -
			// This also implies that the common prefix contains no ':' or '*'
144 -
			// since the existing key can't contain those chars.
145 -
			i := 0
146 -
			max := min(len(path), len(n.path))
147 -
			for i < max && path[i] == n.path[i] {
148 -
				i++
149 -
			}
169 +
			n.children = []*node{&child}
170 +
			// []byte for proper unicode char conversion, see #65
171 +
			n.indices = bytesconv.BytesToString([]byte{n.path[i]})
172 +
			n.path = path[:i]
173 +
			n.handlers = nil
174 +
			n.wildChild = false
175 +
			n.fullPath = fullPath[:parentFullPathIndex+i]
176 +
		}
150 177
151 -
			// Split edge
152 -
			if i < len(n.path) {
153 -
				child := node{
154 -
					path:      n.path[i:],
155 -
					wildChild: n.wildChild,
156 -
					indices:   n.indices,
157 -
					children:  n.children,
158 -
					handlers:  n.handlers,
159 -
					priority:  n.priority - 1,
160 -
					fullPath:  n.fullPath,
161 -
				}
178 +
		// Make new node a child of this node
179 +
		if i < len(path) {
180 +
			path = path[i:]
162 181
163 -
				// Update maxParams (max of all children)
164 -
				for i := range child.children {
165 -
					if child.children[i].maxParams > child.maxParams {
166 -
						child.maxParams = child.children[i].maxParams
167 -
					}
182 +
			if n.wildChild {
183 +
				parentFullPathIndex += len(n.path)
184 +
				n = n.children[0]
185 +
				n.priority++
186 +
187 +
				// Check if the wildcard matches
188 +
				if len(path) >= len(n.path) && n.path == path[:len(n.path)] &&
189 +
					// Adding a child to a catchAll is not possible
190 +
					n.nType != catchAll &&
191 +
					// Check for longer wildcard, e.g. :name and :names
192 +
					(len(n.path) >= len(path) || path[len(n.path)] == '/') {
193 +
					continue walk
168 194
				}
169 195
170 -
				n.children = []*node{&child}
171 -
				// []byte for proper unicode char conversion, see #65
172 -
				n.indices = string([]byte{n.path[i]})
173 -
				n.path = path[:i]
174 -
				n.handlers = nil
175 -
				n.wildChild = false
176 -
				n.fullPath = fullPath[:parentFullPathIndex+i]
196 +
				pathSeg := path
197 +
				if n.nType != catchAll {
198 +
					pathSeg = strings.SplitN(path, "/", 2)[0]
199 +
				}
200 +
				prefix := fullPath[:strings.Index(fullPath, pathSeg)] + n.path
201 +
				panic("'" + pathSeg +
202 +
					"' in new path '" + fullPath +
203 +
					"' conflicts with existing wildcard '" + n.path +
204 +
					"' in existing prefix '" + prefix +
205 +
					"'")
177 206
			}
178 207
179 -
			// Make new node a child of this node
180 -
			if i < len(path) {
181 -
				path = path[i:]
182 -
183 -
				if n.wildChild {
184 -
					parentFullPathIndex += len(n.path)
185 -
					n = n.children[0]
186 -
					n.priority++
187 -
188 -
					// Update maxParams of the child node
189 -
					if numParams > n.maxParams {
190 -
						n.maxParams = numParams
191 -
					}
192 -
					numParams--
193 -
194 -
					// Check if the wildcard matches
195 -
					if len(path) >= len(n.path) && n.path == path[:len(n.path)] {
196 -
						// check for longer wildcard, e.g. :name and :names
197 -
						if len(n.path) >= len(path) || path[len(n.path)] == '/' {
198 -
							continue walk
199 -
						}
200 -
					}
208 +
			c := path[0]
201 209
202 -
					pathSeg := path
203 -
					if n.nType != catchAll {
204 -
						pathSeg = strings.SplitN(path, "/", 2)[0]
205 -
					}
206 -
					prefix := fullPath[:strings.Index(fullPath, pathSeg)] + n.path
207 -
					panic("'" + pathSeg +
208 -
						"' in new path '" + fullPath +
209 -
						"' conflicts with existing wildcard '" + n.path +
210 -
						"' in existing prefix '" + prefix +
211 -
						"'")
212 -
				}
213 -
214 -
				c := path[0]
210 +
			// slash after param
211 +
			if n.nType == param && c == '/' && len(n.children) == 1 {
212 +
				parentFullPathIndex += len(n.path)
213 +
				n = n.children[0]
214 +
				n.priority++
215 +
				continue walk
216 +
			}
215 217
216 -
				// slash after param
217 -
				if n.nType == param && c == '/' && len(n.children) == 1 {
218 +
			// Check if a child with the next path byte exists
219 +
			for i, max := 0, len(n.indices); i < max; i++ {
220 +
				if c == n.indices[i] {
218 221
					parentFullPathIndex += len(n.path)
219 -
					n = n.children[0]
220 -
					n.priority++
222 +
					i = n.incrementChildPrio(i)
223 +
					n = n.children[i]
221 224
					continue walk
222 225
				}
226 +
			}
223 227
224 -
				// Check if a child with the next path byte exists
225 -
				for i := 0; i < len(n.indices); i++ {
226 -
					if c == n.indices[i] {
227 -
						parentFullPathIndex += len(n.path)
228 -
						i = n.incrementChildPrio(i)
229 -
						n = n.children[i]
230 -
						continue walk
231 -
					}
232 -
				}
233 -
234 -
				// Otherwise insert it
235 -
				if c != ':' && c != '*' {
236 -
					// []byte for proper unicode char conversion, see #65
237 -
					n.indices += string([]byte{c})
238 -
					child := &node{
239 -
						maxParams: numParams,
240 -
						fullPath:  fullPath,
241 -
					}
242 -
					n.children = append(n.children, child)
243 -
					n.incrementChildPrio(len(n.indices) - 1)
244 -
					n = child
245 -
				}
246 -
				n.insertChild(numParams, path, fullPath, handlers)
247 -
				return
248 -
249 -
			} else if i == len(path) { // Make node a (in-path) leaf
250 -
				if n.handlers != nil {
251 -
					panic("handlers are already registered for path '" + fullPath + "'")
228 +
			// Otherwise insert it
229 +
			if c != ':' && c != '*' {
230 +
				// []byte for proper unicode char conversion, see #65
231 +
				n.indices += bytesconv.BytesToString([]byte{c})
232 +
				child := &node{
233 +
					fullPath: fullPath,
252 234
				}
253 -
				n.handlers = handlers
235 +
				n.children = append(n.children, child)
236 +
				n.incrementChildPrio(len(n.indices) - 1)
237 +
				n = child
254 238
			}
239 +
			n.insertChild(path, fullPath, handlers)
255 240
			return
256 241
		}
257 -
	} else { // Empty tree
258 -
		n.insertChild(numParams, path, fullPath, handlers)
259 -
		n.nType = root
242 +
243 +
		// Otherwise and handle to current node
244 +
		if n.handlers != nil {
245 +
			panic("handlers are already registered for path '" + fullPath + "'")
246 +
		}
247 +
		n.handlers = handlers
248 +
		n.fullPath = fullPath
249 +
		return
260 250
	}
261 251
}
262 252
263 -
func (n *node) insertChild(numParams uint8, path string, fullPath string, handlers HandlersChain) {
264 -
	var offset int // already handled bytes of the path
265 -
266 -
	// find prefix until first wildcard (beginning with ':' or '*')
267 -
	for i, max := 0, len(path); numParams > 0; i++ {
268 -
		c := path[i]
253 +
// Search for a wildcard segment and check the name for invalid characters.
254 +
// Returns -1 as index, if no wildcard was found.
255 +
func findWildcard(path string) (wildcard string, i int, valid bool) {
256 +
	// Find start
257 +
	for start, c := range []byte(path) {
258 +
		// A wildcard starts with ':' (param) or '*' (catch-all)
269 259
		if c != ':' && c != '*' {
270 260
			continue
271 261
		}
272 262
273 -
		// find wildcard end (either '/' or path end)
274 -
		end := i + 1
275 -
		for end < max && path[end] != '/' {
276 -
			switch path[end] {
277 -
			// the wildcard name must not contain ':' and '*'
263 +
		// Find end and check for invalid characters
264 +
		valid = true
265 +
		for end, c := range []byte(path[start+1:]) {
266 +
			switch c {
267 +
			case '/':
268 +
				return path[start : start+1+end], start, valid
278 269
			case ':', '*':
279 -
				panic("only one wildcard per path segment is allowed, has: '" +
280 -
					path[i:] + "' in path '" + fullPath + "'")
281 -
			default:
282 -
				end++
270 +
				valid = false
283 271
			}
284 272
		}
273 +
		return path[start:], start, valid
274 +
	}
275 +
	return "", -1, false
276 +
}
285 277
286 -
		// check if this Node existing children which would be
287 -
		// unreachable if we insert the wildcard here
288 -
		if len(n.children) > 0 {
289 -
			panic("wildcard route '" + path[i:end] +
290 -
				"' conflicts with existing children in path '" + fullPath + "'")
278 +
func (n *node) insertChild(path string, fullPath string, handlers HandlersChain) {
279 +
	for {
280 +
		// Find prefix until first wildcard
281 +
		wildcard, i, valid := findWildcard(path)
282 +
		if i < 0 { // No wildcard found
283 +
			break
284 +
		}
285 +
286 +
		// The wildcard name must not contain ':' and '*'
287 +
		if !valid {
288 +
			panic("only one wildcard per path segment is allowed, has: '" +
289 +
				wildcard + "' in path '" + fullPath + "'")
291 290
		}
292 291
293 292
		// check if the wildcard has a name
294 -
		if end-i < 2 {
293 +
		if len(wildcard) < 2 {
295 294
			panic("wildcards must be named with a non-empty name in path '" + fullPath + "'")
296 295
		}
297 296
298 -
		if c == ':' { // param
299 -
			// split path at the beginning of the wildcard
297 +
		// Check if this node has existing children which would be
298 +
		// unreachable if we insert the wildcard here
299 +
		if len(n.children) > 0 {
300 +
			panic("wildcard segment '" + wildcard +
301 +
				"' conflicts with existing children in path '" + fullPath + "'")
302 +
		}
303 +
304 +
		if wildcard[0] == ':' { // param
300 305
			if i > 0 {
301 -
				n.path = path[offset:i]
302 -
				offset = i
306 +
				// Insert prefix before the current wildcard
307 +
				n.path = path[:i]
308 +
				path = path[i:]
303 309
			}
304 310
311 +
			n.wildChild = true
305 312
			child := &node{
306 -
				nType:     param,
307 -
				maxParams: numParams,
308 -
				fullPath:  fullPath,
313 +
				nType:    param,
314 +
				path:     wildcard,
315 +
				fullPath: fullPath,
309 316
			}
310 317
			n.children = []*node{child}
311 -
			n.wildChild = true
312 318
			n = child
313 319
			n.priority++
314 -
			numParams--
315 320
316 321
			// if the path doesn't end with the wildcard, then there
317 322
			// will be another non-wildcard subpath starting with '/'
318 -
			if end < max {
319 -
				n.path = path[offset:end]
320 -
				offset = end
323 +
			if len(wildcard) < len(path) {
324 +
				path = path[len(wildcard):]
321 325
322 326
				child := &node{
323 -
					maxParams: numParams,
324 -
					priority:  1,
325 -
					fullPath:  fullPath,
327 +
					priority: 1,
328 +
					fullPath: fullPath,
326 329
				}
327 330
				n.children = []*node{child}
328 331
				n = child
332 +
				continue
329 333
			}
330 334
331 -
		} else { // catchAll
332 -
			if end != max || numParams > 1 {
333 -
				panic("catch-all routes are only allowed at the end of the path in path '" + fullPath + "'")
334 -
			}
335 +
			// Otherwise we're done. Insert the handle in the new leaf
336 +
			n.handlers = handlers
337 +
			return
338 +
		}
335 339
336 -
			if len(n.path) > 0 && n.path[len(n.path)-1] == '/' {
337 -
				panic("catch-all conflicts with existing handle for the path segment root in path '" + fullPath + "'")
338 -
			}
340 +
		// catchAll
341 +
		if i+len(wildcard) != len(path) {
342 +
			panic("catch-all routes are only allowed at the end of the path in path '" + fullPath + "'")
343 +
		}
339 344
340 -
			// currently fixed width 1 for '/'
341 -
			i--
342 -
			if path[i] != '/' {
343 -
				panic("no / before catch-all in path '" + fullPath + "'")
344 -
			}
345 +
		if len(n.path) > 0 && n.path[len(n.path)-1] == '/' {
346 +
			panic("catch-all conflicts with existing handle for the path segment root in path '" + fullPath + "'")
347 +
		}
345 348
346 -
			n.path = path[offset:i]
349 +
		// currently fixed width 1 for '/'
350 +
		i--
351 +
		if path[i] != '/' {
352 +
			panic("no / before catch-all in path '" + fullPath + "'")
353 +
		}
347 354
348 -
			// first node: catchAll node with empty path
349 -
			child := &node{
350 -
				wildChild: true,
351 -
				nType:     catchAll,
352 -
				maxParams: 1,
353 -
				fullPath:  fullPath,
354 -
			}
355 -
			n.children = []*node{child}
356 -
			n.indices = string(path[i])
357 -
			n = child
358 -
			n.priority++
355 +
		n.path = path[:i]
359 356
360 -
			// second node: node holding the variable
361 -
			child = &node{
362 -
				path:      path[i:],
363 -
				nType:     catchAll,
364 -
				maxParams: 1,
365 -
				handlers:  handlers,
366 -
				priority:  1,
367 -
				fullPath:  fullPath,
368 -
			}
369 -
			n.children = []*node{child}
357 +
		// First node: catchAll node with empty path
358 +
		child := &node{
359 +
			wildChild: true,
360 +
			nType:     catchAll,
361 +
			fullPath:  fullPath,
362 +
		}
370 363
371 -
			return
364 +
		n.children = []*node{child}
365 +
		n.indices = string('/')
366 +
		n = child
367 +
		n.priority++
368 +
369 +
		// second node: node holding the variable
370 +
		child = &node{
371 +
			path:     path[i:],
372 +
			nType:    catchAll,
373 +
			handlers: handlers,
374 +
			priority: 1,
375 +
			fullPath: fullPath,
372 376
		}
377 +
		n.children = []*node{child}
378 +
379 +
		return
373 380
	}
374 381
375 -
	// insert remaining path part and handle to the leaf
376 -
	n.path = path[offset:]
382 +
	// If no wildcard was found, simply insert the path and handle
383 +
	n.path = path
377 384
	n.handlers = handlers
378 385
	n.fullPath = fullPath
379 386
}
@@ -381,30 +388,30 @@
Loading
381 388
// nodeValue holds return values of (*Node).getValue method
382 389
type nodeValue struct {
383 390
	handlers HandlersChain
384 -
	params   Params
391 +
	params   *Params
385 392
	tsr      bool
386 393
	fullPath string
387 394
}
388 395
389 -
// getValue returns the handle registered with the given path (key). The values of
396 +
// Returns the handle registered with the given path (key). The values of
390 397
// wildcards are saved to a map.
391 398
// If no handle can be found, a TSR (trailing slash redirect) recommendation is
392 399
// made if a handle exists with an extra (without the) trailing slash for the
393 400
// given path.
394 -
func (n *node) getValue(path string, po Params, unescape bool) (value nodeValue) {
395 -
	value.params = po
401 +
func (n *node) getValue(path string, params *Params, unescape bool) (value nodeValue) {
396 402
walk: // Outer loop for walking the tree
397 403
	for {
398 -
		if len(path) > len(n.path) {
399 -
			if path[:len(n.path)] == n.path {
400 -
				path = path[len(n.path):]
404 +
		prefix := n.path
405 +
		if len(path) > len(prefix) {
406 +
			if path[:len(prefix)] == prefix {
407 +
				path = path[len(prefix):]
401 408
				// If this node does not have a wildcard (param or catchAll)
402 -
				// child,  we can just look up the next child node and continue
409 +
				// child, we can just look up the next child node and continue
403 410
				// to walk down the tree
404 411
				if !n.wildChild {
405 -
					c := path[0]
406 -
					for i := 0; i < len(n.indices); i++ {
407 -
						if c == n.indices[i] {
412 +
					idxc := path[0]
413 +
					for i, c := range []byte(n.indices) {
414 +
						if c == idxc {
408 415
							n = n.children[i]
409 416
							continue walk
410 417
						}
@@ -413,35 +420,38 @@
Loading
413 420
					// Nothing found.
414 421
					// We can recommend to redirect to the same URL without a
415 422
					// trailing slash if a leaf exists for that path.
416 -
					value.tsr = path == "/" && n.handlers != nil
423 +
					value.tsr = (path == "/" && n.handlers != nil)
417 424
					return
418 425
				}
419 426
420 -
				// handle wildcard child
427 +
				// Handle wildcard child
421 428
				n = n.children[0]
422 429
				switch n.nType {
423 430
				case param:
424 -
					// find param end (either '/' or path end)
431 +
					// Find param end (either '/' or path end)
425 432
					end := 0
426 433
					for end < len(path) && path[end] != '/' {
427 434
						end++
428 435
					}
429 436
430 -
					// save param value
431 -
					if cap(value.params) < int(n.maxParams) {
432 -
						value.params = make(Params, 0, n.maxParams)
433 -
					}
434 -
					i := len(value.params)
435 -
					value.params = value.params[:i+1] // expand slice within preallocated capacity
436 -
					value.params[i].Key = n.path[1:]
437 -
					val := path[:end]
438 -
					if unescape {
439 -
						var err error
440 -
						if value.params[i].Value, err = url.QueryUnescape(val); err != nil {
441 -
							value.params[i].Value = val // fallback, in case of error
437 +
					// Save param value
438 +
					if params != nil {
439 +
						if value.params == nil {
440 +
							value.params = params
441 +
						}
442 +
						// Expand slice within preallocated capacity
443 +
						i := len(*value.params)
444 +
						*value.params = (*value.params)[:i+1]
445 +
						val := path[:end]
446 +
						if unescape {
447 +
							if v, err := url.QueryUnescape(val); err == nil {
448 +
								val = v
449 +
							}
450 +
						}
451 +
						(*value.params)[i] = Param{
452 +
							Key:   n.path[1:],
453 +
							Value: val,
442 454
						}
443 -
					} else {
444 -
						value.params[i].Value = val
445 455
					}
446 456
447 457
					// we need to go deeper!
@@ -453,7 +463,7 @@
Loading
453 463
						}
454 464
455 465
						// ... but we can't
456 -
						value.tsr = len(path) == end+1
466 +
						value.tsr = (len(path) == end+1)
457 467
						return
458 468
					}
459 469
@@ -465,26 +475,29 @@
Loading
465 475
						// No handle found. Check if a handle for this path + a
466 476
						// trailing slash exists for TSR recommendation
467 477
						n = n.children[0]
468 -
						value.tsr = n.path == "/" && n.handlers != nil
478 +
						value.tsr = (n.path == "/" && n.handlers != nil)
469 479
					}
470 -
471 480
					return
472 481
473 482
				case catchAll:
474 -
					// save param value
475 -
					if cap(value.params) < int(n.maxParams) {
476 -
						value.params = make(Params, 0, n.maxParams)
477 -
					}
478 -
					i := len(value.params)
479 -
					value.params = value.params[:i+1] // expand slice within preallocated capacity
480 -
					value.params[i].Key = n.path[2:]
481 -
					if unescape {
482 -
						var err error
483 -
						if value.params[i].Value, err = url.QueryUnescape(path); err != nil {
484 -
							value.params[i].Value = path // fallback, in case of error
483 +
					// Save param value
484 +
					if params != nil {
485 +
						if value.params == nil {
486 +
							value.params = params
487 +
						}
488 +
						// Expand slice within preallocated capacity
489 +
						i := len(*value.params)
490 +
						*value.params = (*value.params)[:i+1]
491 +
						val := path
492 +
						if unescape {
493 +
							if v, err := url.QueryUnescape(path); err == nil {
494 +
								val = v
495 +
							}
496 +
						}
497 +
						(*value.params)[i] = Param{
498 +
							Key:   n.path[2:],
499 +
							Value: val,
485 500
						}
486 -
					} else {
487 -
						value.params[i].Value = path
488 501
					}
489 502
490 503
					value.handlers = n.handlers
@@ -495,7 +508,9 @@
Loading
495 508
					panic("invalid node type")
496 509
				}
497 510
			}
498 -
		} else if path == n.path {
511 +
		}
512 +
513 +
		if path == prefix {
499 514
			// We should have reached the node containing the handle.
500 515
			// Check if this node has a handle registered.
501 516
			if value.handlers = n.handlers; value.handlers != nil {
@@ -503,6 +518,9 @@
Loading
503 518
				return
504 519
			}
505 520
521 +
			// If there is no handle for this route, but this route has a
522 +
			// wildcard child, there must be a handle for this path with an
523 +
			// additional trailing slash
506 524
			if path == "/" && n.wildChild && n.nType != root {
507 525
				value.tsr = true
508 526
				return
@@ -510,8 +528,8 @@
Loading
510 528
511 529
			// No handle found. Check if a handle for this path + a
512 530
			// trailing slash exists for trailing slash recommendation
513 -
			for i := 0; i < len(n.indices); i++ {
514 -
				if n.indices[i] == '/' {
531 +
			for i, c := range []byte(n.indices) {
532 +
				if c == '/' {
515 533
					n = n.children[i]
516 534
					value.tsr = (len(n.path) == 1 && n.handlers != nil) ||
517 535
						(n.nType == catchAll && n.children[0].handlers != nil)
@@ -525,22 +543,61 @@
Loading
525 543
		// Nothing found. We can recommend to redirect to the same URL with an
526 544
		// extra trailing slash if a leaf exists for that path
527 545
		value.tsr = (path == "/") ||
528 -
			(len(n.path) == len(path)+1 && n.path[len(path)] == '/' &&
529 -
				path == n.path[:len(n.path)-1] && n.handlers != nil)
546 +
			(len(prefix) == len(path)+1 && prefix[len(path)] == '/' &&
547 +
				path == prefix[:len(prefix)-1] && n.handlers != nil)
530 548
		return
531 549
	}
532 550
}
533 551
534 -
// findCaseInsensitivePath makes a case-insensitive lookup of the given path and tries to find a handler.
552 +
// Makes a case-insensitive lookup of the given path and tries to find a handler.
535 553
// It can optionally also fix trailing slashes.
536 554
// It returns the case-corrected path and a bool indicating whether the lookup
537 555
// was successful.
538 -
func (n *node) findCaseInsensitivePath(path string, fixTrailingSlash bool) (ciPath []byte, found bool) {
539 -
	ciPath = make([]byte, 0, len(path)+1) // preallocate enough memory
556 +
func (n *node) findCaseInsensitivePath(path string, fixTrailingSlash bool) ([]byte, bool) {
557 +
	const stackBufSize = 128
558 +
559 +
	// Use a static sized buffer on the stack in the common case.
560 +
	// If the path is too long, allocate a buffer on the heap instead.
561 +
	buf := make([]byte, 0, stackBufSize)
562 +
	if l := len(path) + 1; l > stackBufSize {
563 +
		buf = make([]byte, 0, l)
564 +
	}
540 565
541 -
	// Outer loop for walking the tree
542 -
	for len(path) >= len(n.path) && strings.EqualFold(path[:len(n.path)], n.path) {
543 -
		path = path[len(n.path):]
566 +
	ciPath := n.findCaseInsensitivePathRec(
567 +
		path,
568 +
		buf,       // Preallocate enough memory for new path
569 +
		[4]byte{}, // Empty rune buffer
570 +
		fixTrailingSlash,
571 +
	)
572 +
573 +
	return ciPath, ciPath != nil
574 +
}
575 +
576 +
// Shift bytes in array by n bytes left
577 +
func shiftNRuneBytes(rb [4]byte, n int) [4]byte {
578 +
	switch n {
579 +
	case 0:
580 +
		return rb
581 +
	case 1:
582 +
		return [4]byte{rb[1], rb[2], rb[3], 0}
583 +
	case 2:
584 +
		return [4]byte{rb[2], rb[3]}
585 +
	case 3:
586 +
		return [4]byte{rb[3]}
587 +
	default:
588 +
		return [4]byte{}
589 +
	}
590 +
}
591 +
592 +
// Recursive case-insensitive lookup function used by n.findCaseInsensitivePath
593 +
func (n *node) findCaseInsensitivePathRec(path string, ciPath []byte, rb [4]byte, fixTrailingSlash bool) []byte {
594 +
	npLen := len(n.path)
595 +
596 +
walk: // Outer loop for walking the tree
597 +
	for len(path) >= npLen && (npLen == 0 || strings.EqualFold(path[1:npLen], n.path[1:])) {
598 +
		// Add common prefix to result
599 +
		oldPath := path
600 +
		path = path[npLen:]
544 601
		ciPath = append(ciPath, n.path...)
545 602
546 603
		if len(path) > 0 {
@@ -548,65 +605,132 @@
Loading
548 605
			// we can just look up the next child node and continue to walk down
549 606
			// the tree
550 607
			if !n.wildChild {
551 -
				r := unicode.ToLower(rune(path[0]))
552 -
				for i, index := range n.indices {
553 -
					// must use recursive approach since both index and
554 -
					// ToLower(index) could exist. We must check both.
555 -
					if r == unicode.ToLower(index) {
556 -
						out, found := n.children[i].findCaseInsensitivePath(path, fixTrailingSlash)
557 -
						if found {
558 -
							return append(ciPath, out...), true
608 +
				// Skip rune bytes already processed
609 +
				rb = shiftNRuneBytes(rb, npLen)
610 +
611 +
				if rb[0] != 0 {
612 +
					// Old rune not finished
613 +
					idxc := rb[0]
614 +
					for i, c := range []byte(n.indices) {
615 +
						if c == idxc {
616 +
							// continue with child node
617 +
							n = n.children[i]
618 +
							npLen = len(n.path)
619 +
							continue walk
620 +
						}
621 +
					}
622 +
				} else {
623 +
					// Process a new rune
624 +
					var rv rune
625 +
626 +
					// Find rune start.
627 +
					// Runes are up to 4 byte long,
628 +
					// -4 would definitely be another rune.
629 +
					var off int
630 +
					for max := min(npLen, 3); off < max; off++ {
631 +
						if i := npLen - off; utf8.RuneStart(oldPath[i]) {
632 +
							// read rune from cached path
633 +
							rv, _ = utf8.DecodeRuneInString(oldPath[i:])
634 +
							break
635 +
						}
636 +
					}
637 +
638 +
					// Calculate lowercase bytes of current rune
639 +
					lo := unicode.ToLower(rv)
640 +
					utf8.EncodeRune(rb[:], lo)
641 +
642 +
					// Skip already processed bytes
643 +
					rb = shiftNRuneBytes(rb, off)
644 +
645 +
					idxc := rb[0]
646 +
					for i, c := range []byte(n.indices) {
647 +
						// Lowercase matches
648 +
						if c == idxc {
649 +
							// must use a recursive approach since both the
650 +
							// uppercase byte and the lowercase byte might exist
651 +
							// as an index
652 +
							if out := n.children[i].findCaseInsensitivePathRec(
653 +
								path, ciPath, rb, fixTrailingSlash,
654 +
							); out != nil {
655 +
								return out
656 +
							}
657 +
							break
658 +
						}
659 +
					}
660 +
661 +
					// If we found no match, the same for the uppercase rune,
662 +
					// if it differs
663 +
					if up := unicode.ToUpper(rv); up != lo {
664 +
						utf8.EncodeRune(rb[:], up)
665 +
						rb = shiftNRuneBytes(rb, off)
666 +
667 +
						idxc := rb[0]
668 +
						for i, c := range []byte(n.indices) {
669 +
							// Uppercase matches
670 +
							if c == idxc {
671 +
								// Continue with child node
672 +
								n = n.children[i]
673 +
								npLen = len(n.path)
674 +
								continue walk
675 +
							}
559 676
						}
560 677
					}
561 678
				}
562 679
563 680
				// Nothing found. We can recommend to redirect to the same URL
564 681
				// without a trailing slash if a leaf exists for that path
565 -
				found = fixTrailingSlash && path == "/" && n.handlers != nil
566 -
				return
682 +
				if fixTrailingSlash && path == "/" && n.handlers != nil {
683 +
					return ciPath
684 +
				}
685 +
				return nil
567 686
			}
568 687
569 688
			n = n.children[0]
570 689
			switch n.nType {
571 690
			case param:
572 -
				// find param end (either '/' or path end)
573 -
				k := 0
574 -
				for k < len(path) && path[k] != '/' {
575 -
					k++
691 +
				// Find param end (either '/' or path end)
692 +
				end := 0
693 +
				for end < len(path) && path[end] != '/' {
694 +
					end++
576 695
				}
577 696
578 -
				// add param value to case insensitive path
579 -
				ciPath = append(ciPath, path[:k]...)
697 +
				// Add param value to case insensitive path
698 +
				ciPath = append(ciPath, path[:end]...)
580 699
581 -
				// we need to go deeper!
582 -
				if k < len(path) {
700 +
				// We need to go deeper!
701 +
				if end < len(path) {
583 702
					if len(n.children) > 0 {
584 -
						path = path[k:]
703 +
						// Continue with child node
585 704
						n = n.children[0]
705 +
						npLen = len(n.path)
706 +
						path = path[end:]
586 707
						continue
587 708
					}
588 709
589 710
					// ... but we can't
590 -
					if fixTrailingSlash && len(path) == k+1 {
591 -
						return ciPath, true
711 +
					if fixTrailingSlash && len(path) == end+1 {
712 +
						return ciPath
592 713
					}
593 -
					return
714 +
					return nil
594 715
				}
595 716
596 717
				if n.handlers != nil {
597 -
					return ciPath, true
598 -
				} else if fixTrailingSlash && len(n.children) == 1 {
718 +
					return ciPath
719 +
				}
720 +
721 +
				if fixTrailingSlash && len(n.children) == 1 {
599 722
					// No handle found. Check if a handle for this path + a
600 723
					// trailing slash exists
601 724
					n = n.children[0]
602 725
					if n.path == "/" && n.handlers != nil {
603 -
						return append(ciPath, '/'), true
726 +
						return append(ciPath, '/')
604 727
					}
605 728
				}
606 -
				return
729 +
730 +
				return nil
607 731
608 732
			case catchAll:
609 -
				return append(ciPath, path...), true
733 +
				return append(ciPath, path...)
610 734
611 735
			default:
612 736
				panic("invalid node type")
@@ -615,24 +739,24 @@
Loading
615 739
			// We should have reached the node containing the handle.
616 740
			// Check if this node has a handle registered.
617 741
			if n.handlers != nil {
618 -
				return ciPath, true
742 +
				return ciPath
619 743
			}
620 744
621 745
			// No handle found.
622 746
			// Try to fix the path by adding a trailing slash
623 747
			if fixTrailingSlash {
624 -
				for i := 0; i < len(n.indices); i++ {
625 -
					if n.indices[i] == '/' {
748 +
				for i, c := range []byte(n.indices) {
749 +
					if c == '/' {
626 750
						n = n.children[i]
627 751
						if (len(n.path) == 1 && n.handlers != nil) ||
628 752
							(n.nType == catchAll && n.children[0].handlers != nil) {
629 -
							return append(ciPath, '/'), true
753 +
							return append(ciPath, '/')
630 754
						}
631 -
						return
755 +
						return nil
632 756
					}
633 757
				}
634 758
			}
635 -
			return
759 +
			return nil
636 760
		}
637 761
	}
638 762
@@ -640,13 +764,12 @@
Loading
640 764
	// Try to fix the path by adding / removing a trailing slash
641 765
	if fixTrailingSlash {
642 766
		if path == "/" {
643 -
			return ciPath, true
767 +
			return ciPath
644 768
		}
645 -
		if len(path)+1 == len(n.path) && n.path[len(path)] == '/' &&
646 -
			strings.EqualFold(path, n.path[:len(path)]) &&
647 -
			n.handlers != nil {
648 -
			return append(ciPath, n.path...), true
769 +
		if len(path)+1 == npLen && n.path[len(path)] == '/' &&
770 +
			strings.EqualFold(path[1:], n.path[1:len(path)]) && n.handlers != nil {
771 +
			return append(ciPath, n.path...)
649 772
		}
650 773
	}
651 -
	return
774 +
	return nil
652 775
}

@@ -19,13 +19,17 @@
Loading
19 19
//
20 20
// If the result of this process is an empty string, "/" is returned.
21 21
func cleanPath(p string) string {
22 +
	const stackBufSize = 128
22 23
	// Turn empty string into "/"
23 24
	if p == "" {
24 25
		return "/"
25 26
	}
26 27
28 +
	// Reasonably sized buffer on stack to avoid allocations in the common case.
29 +
	// If a larger buffer is required, it gets allocated dynamically.
30 +
	buf := make([]byte, 0, stackBufSize)
31 +
27 32
	n := len(p)
28 -
	var buf []byte
29 33
30 34
	// Invariants:
31 35
	//      reading from path; r is index of next byte to process.
@@ -37,15 +41,21 @@
Loading
37 41
38 42
	if p[0] != '/' {
39 43
		r = 0
40 -
		buf = make([]byte, n+1)
44 +
45 +
		if n+1 > stackBufSize {
46 +
			buf = make([]byte, n+1)
47 +
		} else {
48 +
			buf = buf[:n+1]
49 +
		}
41 50
		buf[0] = '/'
42 51
	}
43 52
44 53
	trailing := n > 1 && p[n-1] == '/'
45 54
46 55
	// A bit more clunky without a 'lazybuf' like the path package, but the loop
47 -
	// gets completely inlined (bufApp). So in contrast to the path package this
48 -
	// loop has no expensive function calls (except 1x make)
56 +
	// gets completely inlined (bufApp calls).
57 +
	// loop has no expensive function calls (except 1x make)		// So in contrast to the path package this loop has no expensive function
58 +
	// calls (except make, if needed).
49 59
50 60
	for r < n {
51 61
		switch {
@@ -69,7 +79,7 @@
Loading
69 79
				// can backtrack
70 80
				w--
71 81
72 -
				if buf == nil {
82 +
				if len(buf) == 0 {
73 83
					for w > 1 && p[w] != '/' {
74 84
						w--
75 85
					}
@@ -81,14 +91,14 @@
Loading
81 91
			}
82 92
83 93
		default:
84 -
			// real path element.
85 -
			// add slash if needed
94 +
			// Real path element.
95 +
			// Add slash if needed
86 96
			if w > 1 {
87 97
				bufApp(&buf, p, w, '/')
88 98
				w++
89 99
			}
90 100
91 -
			// copy element
101 +
			// Copy element
92 102
			for r < n && p[r] != '/' {
93 103
				bufApp(&buf, p, w, p[r])
94 104
				w++
@@ -97,27 +107,44 @@
Loading
97 107
		}
98 108
	}
99 109
100 -
	// re-append trailing slash
110 +
	// Re-append trailing slash
101 111
	if trailing && w > 1 {
102 112
		bufApp(&buf, p, w, '/')
103 113
		w++
104 114
	}
105 115
106 -
	if buf == nil {
116 +
	// If the original string was not modified (or only shortened at the end),
117 +
	// return the respective substring of the original string.
118 +
	// Otherwise return a new string from the buffer.
119 +
	if len(buf) == 0 {
107 120
		return p[:w]
108 121
	}
109 122
	return string(buf[:w])
110 123
}
111 124
112 -
// internal helper to lazily create a buffer if necessary.
125 +
// Internal helper to lazily create a buffer if necessary.
126 +
// Calls to this function get inlined.
113 127
func bufApp(buf *[]byte, s string, w int, c byte) {
114 -
	if *buf == nil {
128 +
	b := *buf
129 +
	if len(b) == 0 {
130 +
		// No modification of the original string so far.
131 +
		// If the next character is the same as in the original string, we do
132 +
		// not yet have to allocate a buffer.
115 133
		if s[w] == c {
116 134
			return
117 135
		}
118 136
119 -
		*buf = make([]byte, len(s))
120 -
		copy(*buf, s[:w])
137 +
		// Otherwise use either the stack buffer, if it is large enough, or
138 +
		// allocate a new buffer on the heap, and copy all previous characters.
139 +
		length := len(s)
140 +
		if length > cap(b) {
141 +
			*buf = make([]byte, length)
142 +
		} else {
143 +
			*buf = (*buf)[:length]
144 +
		}
145 +
		b = *buf
146 +
147 +
		copy(b, s[:w])
121 148
	}
122 -
	(*buf)[w] = c
149 +
	b[w] = c
123 150
}

@@ -18,6 +18,12 @@
Loading
18 18
// interface{} as a Number instead of as a float64.
19 19
var EnableDecoderUseNumber = false
20 20
21 +
// EnableDecoderDisallowUnknownFields is used to call the DisallowUnknownFields method
22 +
// on the JSON Decoder instance. DisallowUnknownFields causes the Decoder to
23 +
// return an error when the destination is a struct and the input contains object
24 +
// keys which do not match any non-ignored, exported fields in the destination.
25 +
var EnableDecoderDisallowUnknownFields = false
26 +
21 27
type jsonBinding struct{}
22 28
23 29
func (jsonBinding) Name() string {
@@ -40,6 +46,9 @@
Loading
40 46
	if EnableDecoderUseNumber {
41 47
		decoder.UseNumber()
42 48
	}
49 +
	if EnableDecoderDisallowUnknownFields {
50 +
		decoder.DisallowUnknownFields()
51 +
	}
43 52
	if err := decoder.Decode(obj); err != nil {
44 53
		return err
45 54
	}

@@ -27,7 +27,6 @@
Loading
27 27
	_ HTMLRender = HTMLDebug{}
28 28
	_ HTMLRender = HTMLProduction{}
29 29
	_ Render     = YAML{}
30 -
	_ Render     = MsgPack{}
31 30
	_ Render     = Reader{}
32 31
	_ Render     = AsciiJSON{}
33 32
	_ Render     = ProtoBuf{}

@@ -16,6 +16,7 @@
Loading
16 16
	"net/url"
17 17
	"os"
18 18
	"strings"
19 +
	"sync"
19 20
	"time"
20 21
21 22
	"github.com/gin-contrib/sse"
@@ -33,9 +34,11 @@
Loading
33 34
	MIMEPOSTForm          = binding.MIMEPOSTForm
34 35
	MIMEMultipartPOSTForm = binding.MIMEMultipartPOSTForm
35 36
	MIMEYAML              = binding.MIMEYAML
36 -
	BodyBytesKey          = "_gin-gonic/gin/bodybyteskey"
37 37
)
38 38
39 +
// BodyBytesKey indicates a default body bytes key.
40 +
const BodyBytesKey = "_gin-gonic/gin/bodybyteskey"
41 +
39 42
const abortIndex int8 = math.MaxInt8 / 2
40 43
41 44
// Context is the most important part of gin. It allows us to pass variables between middleware,
@@ -51,6 +54,10 @@
Loading
51 54
	fullPath string
52 55
53 56
	engine *Engine
57 +
	params *Params
58 +
59 +
	// This mutex protect Keys map
60 +
	mu sync.RWMutex
54 61
55 62
	// Keys is a key/value pair exclusively for the context of each request.
56 63
	Keys map[string]interface{}
@@ -67,6 +74,10 @@
Loading
67 74
	// formCache use url.ParseQuery cached PostForm contains the parsed form data from POST, PATCH,
68 75
	// or PUT body parameters.
69 76
	formCache url.Values
77 +
78 +
	// SameSite allows a server to define a cookie attribute making it impossible for
79 +
	// the browser to send this cookie along with cross-site requests.
80 +
	sameSite http.SameSite
70 81
}
71 82
72 83
/************************************/
@@ -78,18 +89,25 @@
Loading
78 89
	c.Params = c.Params[0:0]
79 90
	c.handlers = nil
80 91
	c.index = -1
92 +
81 93
	c.fullPath = ""
82 94
	c.Keys = nil
83 95
	c.Errors = c.Errors[0:0]
84 96
	c.Accepted = nil
85 97
	c.queryCache = nil
86 98
	c.formCache = nil
99 +
	*c.params = (*c.params)[0:0]
87 100
}
88 101
89 102
// Copy returns a copy of the current context that can be safely used outside the request's scope.
90 103
// This has to be used when the context has to be passed to a goroutine.
91 104
func (c *Context) Copy() *Context {
92 -
	var cp = *c
105 +
	cp := Context{
106 +
		writermem: c.writermem,
107 +
		Request:   c.Request,
108 +
		Params:    c.Params,
109 +
		engine:    c.engine,
110 +
	}
93 111
	cp.writermem.ResponseWriter = nil
94 112
	cp.Writer = &cp.writermem
95 113
	cp.index = abortIndex
@@ -219,16 +237,21 @@
Loading
219 237
// Set is used to store a new key/value pair exclusively for this context.
220 238
// It also lazy initializes  c.Keys if it was not used previously.
221 239
func (c *Context) Set(key string, value interface{}) {
240 +
	c.mu.Lock()
222 241
	if c.Keys == nil {
223 242
		c.Keys = make(map[string]interface{})
224 243
	}
244 +
225 245
	c.Keys[key] = value
246 +
	c.mu.Unlock()
226 247
}
227 248
228 249
// Get returns the value for the given key, ie: (value, true).
229 250
// If the value does not exists it returns (nil, false)
230 251
func (c *Context) Get(key string) (value interface{}, exists bool) {
252 +
	c.mu.RLock()
231 253
	value, exists = c.Keys[key]
254 +
	c.mu.RUnlock()
232 255
	return
233 256
}
234 257
@@ -272,6 +295,22 @@
Loading
272 295
	return
273 296
}
274 297
298 +
// GetUint returns the value associated with the key as an unsigned integer.
299 +
func (c *Context) GetUint(key string) (ui uint) {
300 +
	if val, ok := c.Get(key); ok && val != nil {
301 +
		ui, _ = val.(uint)
302 +
	}
303 +
	return
304 +
}
305 +
306 +
// GetUint64 returns the value associated with the key as an unsigned integer.
307 +
func (c *Context) GetUint64(key string) (ui64 uint64) {
308 +
	if val, ok := c.Get(key); ok && val != nil {
309 +
		ui64, _ = val.(uint64)
310 +
	}
311 +
	return
312 +
}
313 +
275 314
// GetFloat64 returns the value associated with the key as a float64.
276 315
func (c *Context) GetFloat64(key string) (f64 float64) {
277 316
	if val, ok := c.Get(key); ok && val != nil {
@@ -391,17 +430,20 @@
Loading
391 430
	return values
392 431
}
393 432
394 -
func (c *Context) getQueryCache() {
433 +
func (c *Context) initQueryCache() {
395 434
	if c.queryCache == nil {
396 -
		c.queryCache = make(url.Values)
397 -
		c.queryCache, _ = url.ParseQuery(c.Request.URL.RawQuery)
435 +
		if c.Request != nil {
436 +
			c.queryCache = c.Request.URL.Query()
437 +
		} else {
438 +
			c.queryCache = url.Values{}
439 +
		}
398 440
	}
399 441
}
400 442
401 443
// GetQueryArray returns a slice of strings for a given query key, plus
402 444
// a boolean value whether at least one value exists for the given key.
403 445
func (c *Context) GetQueryArray(key string) ([]string, bool) {
404 -
	c.getQueryCache()
446 +
	c.initQueryCache()
405 447
	if values, ok := c.queryCache[key]; ok && len(values) > 0 {
406 448
		return values, true
407 449
	}
@@ -417,7 +459,7 @@
Loading
417 459
// GetQueryMap returns a map for a given query key, plus a boolean value
418 460
// whether at least one value exists for the given key.
419 461
func (c *Context) GetQueryMap(key string) (map[string]string, bool) {
420 -
	c.getQueryCache()
462 +
	c.initQueryCache()
421 463
	return c.get(c.queryCache, key)
422 464
}
423 465
@@ -459,7 +501,7 @@
Loading
459 501
	return values
460 502
}
461 503
462 -
func (c *Context) getFormCache() {
504 +
func (c *Context) initFormCache() {
463 505
	if c.formCache == nil {
464 506
		c.formCache = make(url.Values)
465 507
		req := c.Request
@@ -475,7 +517,7 @@
Loading
475 517
// GetPostFormArray returns a slice of strings for a given form key, plus
476 518
// a boolean value whether at least one value exists for the given key.
477 519
func (c *Context) GetPostFormArray(key string) ([]string, bool) {
478 -
	c.getFormCache()
520 +
	c.initFormCache()
479 521
	if values := c.formCache[key]; len(values) > 0 {
480 522
		return values, true
481 523
	}
@@ -491,13 +533,8 @@
Loading
491 533
// GetPostFormMap returns a map for a given form key, plus a boolean value
492 534
// whether at least one value exists for the given key.
493 535
func (c *Context) GetPostFormMap(key string) (map[string]string, bool) {
494 -
	req := c.Request
495 -
	if err := req.ParseMultipartForm(c.engine.MaxMultipartMemory); err != nil {
496 -
		if err != http.ErrNotMultipart {
497 -
			debugPrint("error on parse multipart form map: %v", err)
498 -
		}
499 -
	}
500 -
	return c.get(req.PostForm, key)
536 +
	c.initFormCache()
537 +
	return c.get(c.formCache, key)
501 538
}
502 539
503 540
// get is an internal method and returns a map which satisfy conditions.
@@ -522,7 +559,11 @@
Loading
522 559
			return nil, err
523 560
		}
524 561
	}
525 -
	_, fh, err := c.Request.FormFile(name)
562 +
	f, fh, err := c.Request.FormFile(name)
563 +
	if err != nil {
564 +
		return nil, err
565 +
	}
566 +
	f.Close()
526 567
	return fh, err
527 568
}
528 569
@@ -760,7 +801,7 @@
Loading
760 801
761 802
// Status sets the HTTP response code.
762 803
func (c *Context) Status(code int) {
763 -
	c.writermem.WriteHeader(code)
804 +
	c.Writer.WriteHeader(code)
764 805
}
765 806
766 807
// Header is a intelligent shortcut for c.Writer.Header().Set(key, value).
@@ -784,6 +825,11 @@
Loading
784 825
	return ioutil.ReadAll(c.Request.Body)
785 826
}
786 827
828 +
// SetSameSite with cookie
829 +
func (c *Context) SetSameSite(samesite http.SameSite) {
830 +
	c.sameSite = samesite
831 +
}
832 +
787 833
// SetCookie adds a Set-Cookie header to the ResponseWriter's headers.
788 834
// The provided cookie must have a valid Name. Invalid cookies may be
789 835
// silently dropped.
@@ -797,6 +843,7 @@
Loading
797 843
		MaxAge:   maxAge,
798 844
		Path:     path,
799 845
		Domain:   domain,
846 +
		SameSite: c.sameSite,
800 847
		Secure:   secure,
801 848
		HttpOnly: httpOnly,
802 849
	})
@@ -850,7 +897,7 @@
Loading
850 897
// Default prepends "while(1)," to response body if the given struct is array values.
851 898
// It also sets the Content-Type as "application/json".
852 899
func (c *Context) SecureJSON(code int, obj interface{}) {
853 -
	c.Render(code, render.SecureJSON{Prefix: c.engine.secureJsonPrefix, Data: obj})
900 +
	c.Render(code, render.SecureJSON{Prefix: c.engine.secureJSONPrefix, Data: obj})
854 901
}
855 902
856 903
// JSONP serializes the given struct as JSON into the response body.
@@ -936,10 +983,21 @@
Loading
936 983
	http.ServeFile(c.Writer, c.Request, filepath)
937 984
}
938 985
986 +
// FileFromFS writes the specified file from http.FileSystem into the body stream in an efficient way.
987 +
func (c *Context) FileFromFS(filepath string, fs http.FileSystem) {
988 +
	defer func(old string) {
989 +
		c.Request.URL.Path = old
990 +
	}(c.Request.URL.Path)
991 +
992 +
	c.Request.URL.Path = filepath
993 +
994 +
	http.FileServer(fs).ServeHTTP(c.Writer, c.Request)
995 +
}
996 +
939 997
// FileAttachment writes the specified file into the body stream in an efficient way
940 998
// On the client side, the file will typically be downloaded with the given filename
941 999
func (c *Context) FileAttachment(filepath, filename string) {
942 -
	c.Writer.Header().Set("content-disposition", fmt.Sprintf("attachment; filename=\"%s\"", filename))
1000 +
	c.Writer.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", filename))
943 1001
	http.ServeFile(c.Writer, c.Request, filepath)
944 1002
}
945 1003
@@ -981,6 +1039,7 @@
Loading
981 1039
	HTMLData interface{}
982 1040
	JSONData interface{}
983 1041
	XMLData  interface{}
1042 +
	YAMLData interface{}
984 1043
	Data     interface{}
985 1044
}
986 1045
@@ -999,6 +1058,10 @@
Loading
999 1058
		data := chooseData(config.XMLData, config.Data)
1000 1059
		c.XML(code, data)
1001 1060
1061 +
	case binding.MIMEYAML:
1062 +
		data := chooseData(config.YAMLData, config.Data)
1063 +
		c.YAML(code, data)
1064 +
1002 1065
	default:
1003 1066
		c.AbortWithError(http.StatusNotAcceptable, errors.New("the accepted formats are not offered by the server")) // nolint: errcheck
1004 1067
	}
@@ -1015,20 +1078,20 @@
Loading
1015 1078
		return offered[0]
1016 1079
	}
1017 1080
	for _, accepted := range c.Accepted {
1018 -
		for _, offert := range offered {
1081 +
		for _, offer := range offered {
1019 1082
			// According to RFC 2616 and RFC 2396, non-ASCII characters are not allowed in headers,
1020 1083
			// therefore we can just iterate over the string without casting it into []rune
1021 1084
			i := 0
1022 1085
			for ; i < len(accepted); i++ {
1023 -
				if accepted[i] == '*' || offert[i] == '*' {
1024 -
					return offert
1086 +
				if accepted[i] == '*' || offer[i] == '*' {
1087 +
					return offer
1025 1088
				}
1026 -
				if accepted[i] != offert[i] {
1089 +
				if accepted[i] != offer[i] {
1027 1090
					break
1028 1091
				}
1029 1092
			}
1030 1093
			if i == len(accepted) {
1031 -
				return offert
1094 +
				return offer
1032 1095
			}
1033 1096
		}
1034 1097
	}
@@ -1044,26 +1107,20 @@
Loading
1044 1107
/***** GOLANG.ORG/X/NET/CONTEXT *****/
1045 1108
/************************************/
1046 1109
1047 -
// Deadline returns the time when work done on behalf of this context
1048 -
// should be canceled. Deadline returns ok==false when no deadline is
1049 -
// set. Successive calls to Deadline return the same results.
1110 +
// Deadline always returns that there is no deadline (ok==false),
1111 +
// maybe you want to use Request.Context().Deadline() instead.
1050 1112
func (c *Context) Deadline() (deadline time.Time, ok bool) {
1051 1113
	return
1052 1114
}
1053 1115
1054 -
// Done returns a channel that's closed when work done on behalf of this
1055 -
// context should be canceled. Done may return nil if this context can
1056 -
// never be canceled. Successive calls to Done return the same value.
1116 +
// Done always returns nil (chan which will wait forever),
1117 +
// if you want to abort your work when the connection was closed
1118 +
// you should use Request.Context().Done() instead.
1057 1119
func (c *Context) Done() <-chan struct{} {
1058 1120
	return nil
1059 1121
}
1060 1122
1061 -
// Err returns a non-nil error value after Done is closed,
1062 -
// successive calls to Err return the same error.
1063 -
// If Done is not yet closed, Err returns nil.
1064 -
// If Done is closed, Err returns a non-nil error explaining why:
1065 -
// Canceled if the context was canceled
1066 -
// or DeadlineExceeded if the context's deadline passed.
1123 +
// Err always returns nil, maybe you want to use Request.Context().Err() instead.
1067 1124
func (c *Context) Err() error {
1068 1125
	return nil
1069 1126
}

@@ -22,6 +22,7 @@
Loading
22 22
	// TestMode indicates gin mode is test.
23 23
	TestMode = "test"
24 24
)
25 +
25 26
const (
26 27
	debugCode = iota
27 28
	releaseCode
@@ -50,8 +51,12 @@
Loading
50 51
51 52
// SetMode sets gin mode according to input string.
52 53
func SetMode(value string) {
54 +
	if value == "" {
55 +
		value = DebugMode
56 +
	}
57 +
53 58
	switch value {
54 -
	case DebugMode, "":
59 +
	case DebugMode:
55 60
		ginMode = debugCode
56 61
	case ReleaseMode:
57 62
		ginMode = releaseCode
@@ -60,9 +65,7 @@
Loading
60 65
	default:
61 66
		panic("gin mode unknown: " + value)
62 67
	}
63 -
	if value == "" {
64 -
		value = DebugMode
65 -
	}
68 +
66 69
	modeName = value
67 70
}
68 71
@@ -71,12 +74,18 @@
Loading
71 74
	binding.Validator = nil
72 75
}
73 76
74 -
// EnableJsonDecoderUseNumber sets true for binding.EnableDecoderUseNumberto to
77 +
// EnableJsonDecoderUseNumber sets true for binding.EnableDecoderUseNumber to
75 78
// call the UseNumber method on the JSON Decoder instance.
76 79
func EnableJsonDecoderUseNumber() {
77 80
	binding.EnableDecoderUseNumber = true
78 81
}
79 82
83 +
// EnableJsonDecoderDisallowUnknownFields sets true for binding.EnableDecoderDisallowUnknownFields to
84 +
// call the DisallowUnknownFields method on the JSON Decoder instance.
85 +
func EnableJsonDecoderDisallowUnknownFields() {
86 +
	binding.EnableDecoderDisallowUnknownFields = true
87 +
}
88 +
80 89
// Mode returns currently gin mode.
81 90
func Mode() string {
82 91
	return modeName

@@ -8,7 +8,7 @@
Loading
8 8
	"reflect"
9 9
	"sync"
10 10
11 -
	"gopkg.in/go-playground/validator.v8"
11 +
	"github.com/go-playground/validator/v10"
12 12
)
13 13
14 14
type defaultValidator struct {
@@ -45,7 +45,7 @@
Loading
45 45
46 46
func (v *defaultValidator) lazyinit() {
47 47
	v.once.Do(func() {
48 -
		config := &validator.Config{TagName: "binding"}
49 -
		v.validate = validator.New(config)
48 +
		v.validate = validator.New()
49 +
		v.validate.SetTagName("binding")
50 50
	})
51 51
}

@@ -2,6 +2,8 @@
Loading
2 2
// Use of this source code is governed by a MIT style
3 3
// license that can be found in the LICENSE file.
4 4
5 +
// +build !nomsgpack
6 +
5 7
package binding
6 8
7 9
import (

@@ -2,6 +2,8 @@
Loading
2 2
// Use of this source code is governed by a MIT style
3 3
// license that can be found in the LICENSE file.
4 4
5 +
// +build !nomsgpack
6 +
5 7
package render
6 8
7 9
import (
@@ -10,6 +12,10 @@
Loading
10 12
	"github.com/ugorji/go/codec"
11 13
)
12 14
15 +
var (
16 +
	_ Render = MsgPack{}
17 +
)
18 +
13 19
// MsgPack contains the given interface object.
14 20
type MsgPack struct {
15 21
	Data interface{}

@@ -2,6 +2,8 @@
Loading
2 2
// Use of this source code is governed by a MIT style
3 3
// license that can be found in the LICENSE file.
4 4
5 +
// +build !nomsgpack
6 +
5 7
package binding
6 8
7 9
import "net/http"
@@ -85,7 +87,7 @@
Loading
85 87
// Default returns the appropriate Binding instance based on the HTTP method
86 88
// and the content type.
87 89
func Default(method, contentType string) Binding {
88 -
	if method == "GET" {
90 +
	if method == http.MethodGet {
89 91
		return Form
90 92
	}
91 93

@@ -6,8 +6,9 @@
Loading
6 6
7 7
import (
8 8
	"fmt"
9 -
	"io"
10 9
	"net/http"
10 +
11 +
	"github.com/gin-gonic/gin/internal/bytesconv"
11 12
)
12 13
13 14
// String contains the given interface object slice and its format.
@@ -35,6 +36,6 @@
Loading
35 36
		_, err = fmt.Fprintf(w, format, data...)
36 37
		return
37 38
	}
38 -
	_, err = io.WriteString(w, format)
39 +
	_, err = w.Write(bytesconv.StringToBytes(format))
39 40
	return
40 41
}

@@ -22,6 +22,9 @@
Loading
22 22
func (r Reader) Render(w http.ResponseWriter) (err error) {
23 23
	r.WriteContentType(w)
24 24
	if r.ContentLength >= 0 {
25 +
		if r.Headers == nil {
26 +
			r.Headers = map[string]string{}
27 +
		}
25 28
		r.Headers["Content-Length"] = strconv.FormatInt(r.ContentLength, 10)
26 29
	}
27 30
	r.writeHeaders(w, r.Headers)
Files Coverage
binding 100.00%
render 92.96%
auth.go 100.00%
context.go 97.52%
debug.go 92.50%
deprecated.go 100.00%
errors.go 100.00%
fs.go 100.00%
gin.go 99.01%
logger.go 100.00%
mode.go 100.00%
path.go 100.00%
recovery.go 97.18%
response_writer.go 93.33%
routergroup.go 100.00%
test_helpers.go 100.00%
tree.go 100.00%
utils.go 96.83%
Project Totals (41 files) 98.47%
4694.1
1.11.x=.11.x
TRAVIS_OS_NAME=linux
4694.3
1.13.x=.13.x
TRAVIS_OS_NAME=linux
4694.5
1.14.x=.14.x
TRAVIS_OS_NAME=linux
4694.2
TRAVIS_OS_NAME=linux
1.12.x=.12.x
4694.9
TRAVIS_OS_NAME=linux
master=