-
Notifications
You must be signed in to change notification settings - Fork 6
/
Copy pathstrict.go
176 lines (158 loc) · 4.83 KB
/
strict.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
// Package strict provides helpers for implementing strict APIs in Martini.
package strict
import (
"net/http"
"strconv"
"strings"
"github.com/go-martini/martini"
)
// HTTP 422 Unprocessable Entity
const StatusUnprocessableEntity = 422
// Negotiator is an interface that can be used to negotiate the content type.
type Negotiator interface {
Accepts(string) float64
ContentType(...string) bool
}
// Negotiator implementation.
type negotiator struct {
r *http.Request
}
// Accept parses the Accept header according to RFC2616 and returns the q value.
func (n *negotiator) Accepts(ctype string) float64 {
return accepts(n.r.Header.Get("Accept"), ctype)
}
// ContentType checks if the Content-Type header matches the passed argument.
func (n *negotiator) ContentType(ctypes ...string) bool {
return checkCT(n.r.Header.Get("Content-Type"), ctypes...)
}
// Strict is a `martini.Handler` that provides a `Negotiator` instance.
func Strict(r *http.Request, c martini.Context) {
c.MapTo(&negotiator{r}, (*Negotiator)(nil))
}
// ContentType generates a handler that writes a 415 Unsupported Media Type response if none of the types match.
// An empty type will allow requests with empty or missing Content-Type header.
func ContentType(ctypes ...string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if contentMethod(r.Method) && !checkCT(r.Header.Get("Content-Type"), ctypes...) {
w.WriteHeader(http.StatusUnsupportedMediaType)
}
}
}
// ContentCharset generates a handler that writes a 415 Unsupported Media Type response if none of the charsets match.
// An empty charset will allow requests with no Content-Type header or no specified charset.
func ContentCharset(charsets ...string) http.HandlerFunc {
for i, c := range charsets {
charsets[i] = strings.ToLower(c)
}
return func(w http.ResponseWriter, r *http.Request) {
if contentMethod(r.Method) && !checkCC(r.Header.Get("Content-Type"), charsets...) {
w.WriteHeader(http.StatusUnsupportedMediaType)
}
}
}
// Accept generates a handler that writes a 406 Not Acceptable response if none of the types match.
func Accept(ctypes ...string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
a := r.Header.Get("Accept")
for _, t := range ctypes {
if accepts(a, t) > 0 {
return
}
}
w.WriteHeader(http.StatusNotAcceptable)
}
}
// MethodNotAllowed writes a 405 Method Not Allowed response when applicable.
// It also sets the Accept header to the list of methods that are acceptable.
func MethodNotAllowed(routes martini.Routes, w http.ResponseWriter, r *http.Request) {
if methods := routes.MethodsFor(r.URL.Path); len(methods) != 0 {
w.Header().Set("Allow", strings.Join(methods, ","))
w.WriteHeader(http.StatusMethodNotAllowed)
}
}
// NotFound writes a 404 Not Found response.
// The difference between this and `http.NotFound` is that this method does not write a body.
func NotFound(w http.ResponseWriter) {
w.WriteHeader(http.StatusNotFound)
}
// RFC2616 header parser (simple version).
func accepts(a, ctype string) (q float64) {
if a == ctype || a == "*/*" || a == "" {
// bail out in some common cases
return 1
}
cGroup, cType := split(ctype, "/")
for _, field := range strings.Split(a, ",") {
found, match := false, false
for i, token := range strings.Split(field, ";") {
if i == 0 {
// token is "type/subtype", "type/*" or "*/*"
aGroup, aType := split(token, "/")
if cType == aType || aType == "*" {
if (aGroup == "*" && aType == "*") || cGroup == aGroup {
// token matches, continue to look for a q value
found = true
continue
}
}
break
}
// token is "key=value"
k, v := split(token, "=")
if k != "q" {
continue
}
// k is "q"
f, err := strconv.ParseFloat(v, 64)
if err != nil {
break
}
if q < f {
q = f
}
match = true
break
}
if found && !match {
q = 1
break
}
}
return
}
// Check the content type against a list of acceptable values.
func checkCT(ct string, ctypes ...string) bool {
ct, _ = split(ct, ";")
for _, t := range ctypes {
if ct == t {
return true
}
}
return false
}
// Check the content encoding against a list of acceptable values.
func checkCC(ce string, charsets ...string) bool {
_, ce = split(strings.ToLower(ce), ";")
_, ce = split(ce, "charset=")
ce, _ = split(ce, ";")
for _, c := range charsets {
if ce == c {
return true
}
}
return false
}
// Check if the request method can contain a content
func contentMethod(m string) bool {
// No Content-Type for GET, HEAD, OPTIONS, DELETE and CONNECT requests.
return m == "POST" || m == "PATCH" || m == "PUT"
}
// Split a string in two parts, cleaning any whitespace.
func split(str, sep string) (a, b string) {
parts := strings.SplitN(str, sep, 2)
a = strings.TrimSpace(parts[0])
if len(parts) == 2 {
b = strings.TrimSpace(parts[1])
}
return
}