fix races in http cors
License: MIT Signed-off-by: Artem Andreenko <mio@volmy.com>
Artem Andreenko committed
Oct 13, 2015 at 01:09 UTC
45550858449935c831c037e5061912c7fb28175f
3 files changed
+76
-33
commands/http/handler.go
+56
-6
@@ -11,6 +11,7 @@ import (
11
"runtime"
12
"strconv"
13
"strings"
14
+ "sync"
15
16
cors "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/rs/cors"
17
context "github.com/ipfs/go-ipfs/Godeps/_workspace/src/golang.org/x/net/context"
@@ -68,8 +69,11 @@ type ServerConfig struct {
69
// Headers is an optional map of headers that is written out.
70
Headers map[string][]string
71
71
- // CORSOpts is a set of options for CORS headers.
72
- CORSOpts *cors.Options
72
+ // cORSOpts is a set of options for CORS headers.
73
+ cORSOpts *cors.Options
74
+
75
+ // cORSOptsRWMutex is a RWMutex for read/write CORSOpts
76
+ cORSOptsRWMutex sync.RWMutex
77
}
78
79
func skipAPIHeader(h string) bool {
@@ -93,7 +97,7 @@ func NewHandler(ctx cmds.Context, root *cmds.Command, cfg *ServerConfig) *Handle
97
// Wrap the internal handler with CORS handling-middleware.
98
// Create a handler for the API.
99
internal := internalHandler{ctx, root, cfg}
96
- c := cors.New(*cfg.CORSOpts)
100
+ c := cors.New(*cfg.cORSOpts)
101
return &Handler{internal, c.Handler(internal)}
102
}
103
@@ -322,6 +326,51 @@ func sanitizedErrStr(err error) string {
326
return s
327
}
328
329
+func NewServerConfig() *ServerConfig {
330
+ cfg := new(ServerConfig)
331
+ cfg.cORSOpts = new(cors.Options)
332
+ return cfg
333
+}
334
+
335
+func (cfg ServerConfig) AllowedOrigins() []string {
336
+ cfg.cORSOptsRWMutex.RLock()
337
+ defer cfg.cORSOptsRWMutex.RUnlock()
338
+ return cfg.cORSOpts.AllowedOrigins
339
+}
340
+
341
+func (cfg *ServerConfig) SetAllowedOrigins(origins ...string) {
342
+ cfg.cORSOptsRWMutex.Lock()
343
+ defer cfg.cORSOptsRWMutex.Unlock()
344
+ cfg.cORSOpts.AllowedOrigins = origins
345
+}
346
+
347
+func (cfg *ServerConfig) AppendAllowedOrigins(origins ...string) {
348
+ cfg.cORSOptsRWMutex.Lock()
349
+ defer cfg.cORSOptsRWMutex.Unlock()
350
+ cfg.cORSOpts.AllowedOrigins = append(cfg.cORSOpts.AllowedOrigins, origins...)
351
+}
352
+
353
+func (cfg ServerConfig) AllowedMethods() []string {
354
+ cfg.cORSOptsRWMutex.RLock()
355
+ defer cfg.cORSOptsRWMutex.RUnlock()
356
+ return []string(cfg.cORSOpts.AllowedMethods)
357
+}
358
+
359
+func (cfg *ServerConfig) SetAllowedMethods(methods ...string) {
360
+ cfg.cORSOptsRWMutex.Lock()
361
+ defer cfg.cORSOptsRWMutex.Unlock()
362
+ if cfg.cORSOpts == nil {
363
+ cfg.cORSOpts = new(cors.Options)
364
+ }
365
+ cfg.cORSOpts.AllowedMethods = methods
366
+}
367
+
368
+func (cfg *ServerConfig) SetAllowCredentials(flag bool) {
369
+ cfg.cORSOptsRWMutex.Lock()
370
+ defer cfg.cORSOptsRWMutex.Unlock()
371
+ cfg.cORSOpts.AllowCredentials = flag
372
+}
373
+
374
// allowOrigin just stops the request if the origin is not allowed.
375
// the CORS middleware apparently does not do this for us...
376
func allowOrigin(r *http.Request, cfg *ServerConfig) bool {
@@ -333,8 +382,8 @@ func allowOrigin(r *http.Request, cfg *ServerConfig) bool {
382
if origin == "" {
383
return true
384
}
336
-
337
- for _, o := range cfg.CORSOpts.AllowedOrigins {
385
+ origins := cfg.AllowedOrigins()
386
+ for _, o := range origins {
387
if o == "*" { // ok! you asked for it!
388
return true
389
}
@@ -375,7 +424,8 @@ func allowReferer(r *http.Request, cfg *ServerConfig) bool {
424
// check CORS ACAOs and pretend Referer works like an origin.
425
// this is valid for many (most?) sane uses of the API in
426
// other applications, and will have the desired effect.
378
- for _, o := range cfg.CORSOpts.AllowedOrigins {
427
+ origins := cfg.AllowedOrigins()
428
+ for _, o := range origins {
429
if o == "*" { // ok! you asked for it!
430
return true
431
}
commands/http/handler_test.go
+4
-8
@@ -6,8 +6,6 @@ import (
6
"net/url"
7
"testing"
8
9
- cors "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/rs/cors"
10
-
9
cmds "github.com/ipfs/go-ipfs/commands"
10
ipfscmd "github.com/ipfs/go-ipfs/core/commands"
11
coremock "github.com/ipfs/go-ipfs/core/mock"
@@ -28,12 +26,10 @@ func assertStatus(t *testing.T, actual, expected int) {
26
}
27
28
func originCfg(origins []string) *ServerConfig {
31
- return &ServerConfig{
32
- CORSOpts: &cors.Options{
33
- AllowedOrigins: origins,
34
- AllowedMethods: []string{"GET", "PUT", "POST"},
35
- },
36
- }
29
+ cfg := NewServerConfig()
30
+ cfg.SetAllowedOrigins(origins...)
31
+ cfg.SetAllowedMethods("GET", "PUT", "POST")
32
+ return cfg
33
}
34
35
type testCase struct {
core/corehttp/commands.go
+16
-19
@@ -7,8 +7,6 @@ import (
7
"strconv"
8
"strings"
9
10
- cors "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/rs/cors"
11
-
10
commands "github.com/ipfs/go-ipfs/commands"
11
cmdsHttp "github.com/ipfs/go-ipfs/commands/http"
12
core "github.com/ipfs/go-ipfs/core"
@@ -41,10 +39,10 @@ func addCORSFromEnv(c *cmdsHttp.ServerConfig) {
39
origin := os.Getenv(originEnvKey)
40
if origin != "" {
41
log.Warning(originEnvKeyDeprecate)
44
- if c.CORSOpts == nil {
45
- c.CORSOpts.AllowedOrigins = []string{origin}
42
+ if len(c.AllowedOrigins()) == 0 {
43
+ c.SetAllowedOrigins([]string{origin}...)
44
}
47
- c.CORSOpts.AllowedOrigins = append(c.CORSOpts.AllowedOrigins, origin)
45
+ c.AppendAllowedOrigins(origin)
46
}
47
}
48
@@ -52,14 +50,14 @@ func addHeadersFromConfig(c *cmdsHttp.ServerConfig, nc *config.Config) {
50
log.Info("Using API.HTTPHeaders:", nc.API.HTTPHeaders)
51
52
if acao := nc.API.HTTPHeaders[cmdsHttp.ACAOrigin]; acao != nil {
55
- c.CORSOpts.AllowedOrigins = acao
53
+ c.SetAllowedOrigins(acao...)
54
}
55
if acam := nc.API.HTTPHeaders[cmdsHttp.ACAMethods]; acam != nil {
58
- c.CORSOpts.AllowedMethods = acam
56
+ c.SetAllowedMethods(acam...)
57
}
58
if acac := nc.API.HTTPHeaders[cmdsHttp.ACACredentials]; acac != nil {
59
for _, v := range acac {
62
- c.CORSOpts.AllowCredentials = (strings.ToLower(v) == "true")
60
+ c.SetAllowCredentials(strings.ToLower(v) == "true")
61
}
62
}
63
@@ -68,13 +66,13 @@ func addHeadersFromConfig(c *cmdsHttp.ServerConfig, nc *config.Config) {
66
67
func addCORSDefaults(c *cmdsHttp.ServerConfig) {
68
// by default use localhost origins
71
- if len(c.CORSOpts.AllowedOrigins) == 0 {
72
- c.CORSOpts.AllowedOrigins = defaultLocalhostOrigins
69
+ if len(c.AllowedOrigins()) == 0 {
70
+ c.SetAllowedOrigins(defaultLocalhostOrigins...)
71
}
72
73
// by default, use GET, PUT, POST
76
- if len(c.CORSOpts.AllowedMethods) == 0 {
77
- c.CORSOpts.AllowedMethods = []string{"GET", "POST", "PUT"}
74
+ if len(c.AllowedMethods()) == 0 {
75
+ c.SetAllowedMethods("GET", "POST", "PUT")
76
}
77
}
78
@@ -90,23 +88,22 @@ func patchCORSVars(c *cmdsHttp.ServerConfig, addr net.Addr) {
88
}
89
90
// we're listening on tcp/udp with ports. ("udp!?" you say? yeah... it happens...)
93
- for i, o := range c.CORSOpts.AllowedOrigins {
91
+ origins := c.AllowedOrigins()
92
+ for i, o := range origins {
93
// TODO: allow replacing <host>. tricky, ip4 and ip6 and hostnames...
94
if port != "" {
95
o = strings.Replace(o, "<port>", port, -1)
96
}
98
- c.CORSOpts.AllowedOrigins[i] = o
97
+ origins[i] = o
98
}
99
+ c.SetAllowedOrigins(origins...)
100
}
101
102
func commandsOption(cctx commands.Context, command *commands.Command) ServeOption {
103
return func(n *core.IpfsNode, l net.Listener, mux *http.ServeMux) (*http.ServeMux, error) {
104
105
- cfg := &cmdsHttp.ServerConfig{
106
- CORSOpts: &cors.Options{
107
- AllowedMethods: []string{"GET", "POST", "PUT"},
108
- },
109
- }
105
+ cfg := cmdsHttp.NewServerConfig()
106
+ cfg.SetAllowedMethods("GET", "POST", "PUT")
107
rcfg, err := n.Repo.Config()
108
if err != nil {
109
return nil, err