From 3ee83a7c5eae50496a9a349330bc70e4d635393c Mon Sep 17 00:00:00 2001 From: Juan Batiz-Benet Date: Fri, 31 Jul 2015 17:36:02 -0400 Subject: [PATCH] fix cors: defaults should take the port of the listener need to do it this way to avoid VERY confusing situations where the user would change the API port (to another port, or maybe even to :0). this way things dont break on the user, and by default, users only need to change the API address and things should still "just work" License: MIT Signed-off-by: Juan Batiz-Benet --- commands/http/handler.go | 36 +++++++++++------------------ commands/http/handler_test.go | 12 ++++++++++ core/corehttp/commands.go | 43 +++++++++++++++++++++++++++++++++++ 3 files changed, 68 insertions(+), 23 deletions(-) diff --git a/commands/http/handler.go b/commands/http/handler.go index 53f63517f..03ef39099 100644 --- a/commands/http/handler.go +++ b/commands/http/handler.go @@ -6,6 +6,7 @@ import ( "fmt" "io" "net/http" + "net/url" "strconv" "strings" @@ -55,13 +56,6 @@ const ( ACACredentials = "Access-Control-Allow-Credentials" ) -var localhostOrigins = []string{ - "http://127.0.0.1", - "https://127.0.0.1", - "http://localhost", - "https://localhost", -} - var mimeTypes = map[string]string{ cmds.JSON: "application/json", cmds.XML: "application/xml", @@ -91,21 +85,7 @@ func skipAPIHeader(h string) bool { func NewHandler(ctx cmds.Context, root *cmds.Command, cfg *ServerConfig) *Handler { if cfg == nil { - cfg = &ServerConfig{} - } - - if cfg.CORSOpts == nil { - cfg.CORSOpts = new(cors.Options) - } - - // by default, use GET, PUT, POST - if cfg.CORSOpts.AllowedMethods == nil { - cfg.CORSOpts.AllowedMethods = []string{"GET", "POST", "PUT"} - } - - // by default, only let 127.0.0.1 through. - if cfg.CORSOpts.AllowedOrigins == nil { - cfg.CORSOpts.AllowedOrigins = localhostOrigins + panic("must provide a valid ServerConfig") } // Wrap the internal handler with CORS handling-middleware. @@ -375,6 +355,16 @@ func allowReferer(r *http.Request, cfg *ServerConfig) bool { return true } + u, err := url.Parse(referer) + if err != nil { + // bad referer. but there _is_ something, so bail. + log.Debug("failed to parse referer: ", referer) + // debug because referer comes straight from the client. dont want to + // let people DOS by putting a huge referer that gets stored in log files. + return false + } + origin := u.Scheme + "://" + u.Host + // check CORS ACAOs and pretend Referer works like an origin. // this is valid for many (most?) sane uses of the API in // other applications, and will have the desired effect. @@ -384,7 +374,7 @@ func allowReferer(r *http.Request, cfg *ServerConfig) bool { } // referer is allowed explicitly - if o == referer { + if o == origin { return true } } diff --git a/commands/http/handler_test.go b/commands/http/handler_test.go index 4539d1641..b61a41457 100644 --- a/commands/http/handler_test.go +++ b/commands/http/handler_test.go @@ -31,6 +31,7 @@ func originCfg(origins []string) *ServerConfig { return &ServerConfig{ CORSOpts: &cors.Options{ AllowedOrigins: origins, + AllowedMethods: []string{"GET", "PUT", "POST"}, }, } } @@ -46,6 +47,13 @@ type testCase struct { ResHeaders map[string]string } +var defaultOrigins = []string{ + "http://localhost", + "http://127.0.0.1", + "https://localhost", + "https://127.0.0.1", +} + func getTestServer(t *testing.T, origins []string) *httptest.Server { cmdsCtx, err := coremock.MockCmdsCtx() if err != nil { @@ -59,6 +67,10 @@ func getTestServer(t *testing.T, origins []string) *httptest.Server { }, } + if len(origins) == 0 { + origins = defaultOrigins + } + handler := NewHandler(cmdsCtx, cmdRoot, originCfg(origins)) return httptest.NewServer(handler) } diff --git a/core/corehttp/commands.go b/core/corehttp/commands.go index 1793c5539..d96818e2a 100644 --- a/core/corehttp/commands.go +++ b/core/corehttp/commands.go @@ -4,6 +4,7 @@ import ( "net" "net/http" "os" + "strconv" "strings" cors "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/rs/cors" @@ -29,6 +30,13 @@ or ipfs daemon --api-http-header 'Access-Control-Allow-Origin: *' ` +var defaultLocalhostOrigins = []string{ + "http://127.0.0.1:", + "https://127.0.0.1:", + "http://localhost:", + "https://localhost:", +} + func addCORSFromEnv(c *cmdsHttp.ServerConfig) { origin := os.Getenv(originEnvKey) if origin != "" { @@ -58,6 +66,39 @@ func addHeadersFromConfig(c *cmdsHttp.ServerConfig, nc *config.Config) { c.Headers = nc.API.HTTPHeaders } +func addCORSDefaults(c *cmdsHttp.ServerConfig) { + // by default use localhost origins + if len(c.CORSOpts.AllowedOrigins) == 0 { + c.CORSOpts.AllowedOrigins = defaultLocalhostOrigins + } + + // by default, use GET, PUT, POST + if len(c.CORSOpts.AllowedMethods) == 0 { + c.CORSOpts.AllowedMethods = []string{"GET", "POST", "PUT"} + } +} + +func patchCORSVars(c *cmdsHttp.ServerConfig, addr net.Addr) { + + // we have to grab the port from an addr, which may be an ip6 addr. + // TODO: this should take multiaddrs and derive port from there. + port := "" + if tcpaddr, ok := addr.(*net.TCPAddr); ok { + port = strconv.Itoa(tcpaddr.Port) + } else if udpaddr, ok := addr.(*net.UDPAddr); ok { + port = strconv.Itoa(udpaddr.Port) + } + + // we're listening on tcp/udp with ports. ("udp!?" you say? yeah... it happens...) + for i, o := range c.CORSOpts.AllowedOrigins { + // TODO: allow replacing . tricky, ip4 and ip6 and hostnames... + if port != "" { + o = strings.Replace(o, "", port, -1) + } + c.CORSOpts.AllowedOrigins[i] = o + } +} + func CommandsOption(cctx commands.Context) ServeOption { return func(n *core.IpfsNode, l net.Listener, mux *http.ServeMux) (*http.ServeMux, error) { @@ -69,6 +110,8 @@ func CommandsOption(cctx commands.Context) ServeOption { addHeadersFromConfig(cfg, n.Repo.Config()) addCORSFromEnv(cfg) + addCORSDefaults(cfg) + patchCORSVars(cfg, l.Addr()) cmdHandler := cmdsHttp.NewHandler(cctx, corecommands.Root, cfg) mux.Handle(cmdsHttp.ApiPath+"/", cmdHandler)