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)