@cryptotaxi247 / kubo / commits / 455508584

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