@cryptotaxi247 / kubo / commits / f6c6d5de1

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 +}