Add CORS middleware handler to the API.
David Braun committed
May 7, 2015 at 16:07 UTC
f6c6d5de13d8a3272707cc453bef49ef480d7711
2 files changed
+108
-13
commands/http/handler.go
+37
-13
@@ -8,6 +8,8 @@ import (
8
"strconv"
9
"strings"
10
11
+ "github.com/rs/cors"
12
+
13
context "github.com/ipfs/go-ipfs/Godeps/_workspace/src/golang.org/x/net/context"
14
15
cmds "github.com/ipfs/go-ipfs/commands"
@@ -16,10 +18,17 @@ import (
18
19
var log = u.Logger("commands/http")
20
21
+// the internal handler for the API
22
+type internalHandler struct {
23
+ ctx cmds.Context
24
+ root *cmds.Command
25
+}
26
+
27
+// The Handler struct is funny because we want to wrap our internal handler
28
+// with CORS while keeping our fields.
29
type Handler struct {
20
- ctx cmds.Context
21
- root *cmds.Command
22
- origin string
30
+ internalHandler
31
+ corsHandler http.Handler
32
}
33
34
var ErrNotFound = errors.New("404 page not found")
@@ -39,16 +48,31 @@ var mimeTypes = map[string]string{
48
cmds.Text: "text/plain",
49
}
50
42
-func NewHandler(ctx cmds.Context, root *cmds.Command, origin string) *Handler {
51
+func NewHandler(ctx cmds.Context, root *cmds.Command, allowedOrigin string) *Handler {
52
// allow whitelisted origins (so we can make API requests from the browser)
44
- if len(origin) > 0 {
45
- log.Info("Allowing API requests from origin: " + origin)
53
+ if len(allowedOrigin) > 0 {
54
+ log.Info("Allowing API requests from origin: " + allowedOrigin)
55
}
56
48
- return &Handler{ctx, root, origin}
57
+ // Create a handler for the API.
58
+ internal := internalHandler{ctx, root}
59
+
60
+ // Create a CORS object for wrapping the internal handler.
61
+ c := cors.New(cors.Options{
62
+ AllowedMethods: []string{"GET", "POST", "PUT"},
63
+
64
+ // use AllowOriginFunc instead of AllowedOrigins because we want to be
65
+ // restrictive by default.
66
+ AllowOriginFunc: func(origin string) bool {
67
+ return (allowedOrigin == "*") || (origin == allowedOrigin)
68
+ },
69
+ })
70
+
71
+ // Wrap the internal handler with CORS handling-middleware.
72
+ return &Handler{internal, c.Handler(internal)}
73
}
74
51
-func (i Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
75
+func (i internalHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
76
log.Debug("Incoming API request: ", r.URL)
77
78
// error on external referers (to prevent CSRF attacks)
@@ -65,11 +89,6 @@ func (i Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
89
return
90
}
91
68
- if len(i.origin) > 0 {
69
- w.Header().Set("Access-Control-Allow-Origin", i.origin)
70
- }
71
- w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
72
-
92
req, err := Parse(r, i.root)
93
if err != nil {
94
if err == ErrNotFound {
@@ -168,6 +187,11 @@ func (i Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
187
flushCopy(w, out)
188
}
189
190
+func (i Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
191
+ // Call the CORS handler which wraps the internal handler.
192
+ i.corsHandler.ServeHTTP(w, r)
193
+}
194
+
195
// flushCopy Copies from an io.Reader to a http.ResponseWriter.
196
// Flushes chunks over HTTP stream as they are read (if supported by transport).
197
func flushCopy(w http.ResponseWriter, out io.Reader) error {
commands/http/handler_test.go
new
+71
@@ -0,0 +1,71 @@
1
+package http
2
+
3
+import (
4
+ "net/http"
5
+ "net/http/httptest"
6
+ "testing"
7
+
8
+ "github.com/ipfs/go-ipfs/commands"
9
+)
10
+
11
+func assertHeaders(t *testing.T, resHeaders http.Header, reqHeaders map[string]string) {
12
+ for name, value := range reqHeaders {
13
+ if resHeaders.Get(name) != value {
14
+ t.Errorf("Invalid header `%s', wanted `%s', got `%s'", name, value, resHeaders.Get(name))
15
+ }
16
+ }
17
+}
18
+
19
+func TestDisallowedOrigin(t *testing.T) {
20
+ res := httptest.NewRecorder()
21
+ req, _ := http.NewRequest("GET", "http://example.com/foo", nil)
22
+ req.Header.Add("Origin", "http://barbaz.com")
23
+
24
+ handler := NewHandler(commands.Context{}, nil, "")
25
+ handler.ServeHTTP(res, req)
26
+
27
+ assertHeaders(t, res.Header(), map[string]string{
28
+ "Access-Control-Allow-Origin": "",
29
+ "Access-Control-Allow-Methods": "",
30
+ "Access-Control-Allow-Credentials": "",
31
+ "Access-Control-Max-Age": "",
32
+ "Access-Control-Expose-Headers": "",
33
+ })
34
+}
35
+
36
+func TestWildcardOrigin(t *testing.T) {
37
+ res := httptest.NewRecorder()
38
+ req, _ := http.NewRequest("GET", "http://example.com/foo", nil)
39
+ req.Header.Add("Origin", "http://foobar.com")
40
+
41
+ handler := NewHandler(commands.Context{}, nil, "*")
42
+ handler.ServeHTTP(res, req)
43
+
44
+ assertHeaders(t, res.Header(), map[string]string{
45
+ "Access-Control-Allow-Origin": "http://foobar.com",
46
+ "Access-Control-Allow-Methods": "",
47
+ "Access-Control-Allow-Headers": "",
48
+ "Access-Control-Allow-Credentials": "",
49
+ "Access-Control-Max-Age": "",
50
+ "Access-Control-Expose-Headers": "",
51
+ })
52
+}
53
+
54
+func TestAllowedMethod(t *testing.T) {
55
+ res := httptest.NewRecorder()
56
+ req, _ := http.NewRequest("OPTIONS", "http://example.com/foo", nil)
57
+ req.Header.Add("Origin", "http://www.foobar.com")
58
+ req.Header.Add("Access-Control-Request-Method", "PUT")
59
+
60
+ handler := NewHandler(commands.Context{}, nil, "http://www.foobar.com")
61
+ handler.ServeHTTP(res, req)
62
+
63
+ assertHeaders(t, res.Header(), map[string]string{
64
+ "Access-Control-Allow-Origin": "http://www.foobar.com",
65
+ "Access-Control-Allow-Methods": "PUT",
66
+ "Access-Control-Allow-Headers": "",
67
+ "Access-Control-Allow-Credentials": "",
68
+ "Access-Control-Max-Age": "",
69
+ "Access-Control-Expose-Headers": "",
70
+ })
71
+}