diff --git a/cmd/ipfs/daemon.go b/cmd/ipfs/daemon.go index d14770ebf8c..0b23d079db7 100644 --- a/cmd/ipfs/daemon.go +++ b/cmd/ipfs/daemon.go @@ -3,6 +3,7 @@ package main import ( _ "expvar" "fmt" + "net" "net/http" _ "net/http/pprof" "os" @@ -126,8 +127,8 @@ future version, along with this notice. Please move to setting the HTTP Headers. // mostly useful to hook up things that register in the default muxer, // and don't provide a convenient http.Handler entry point, such as // expvar and http/pprof. -func defaultMux(path string) func(node *core.IpfsNode, mux *http.ServeMux) (*http.ServeMux, error) { - return func(node *core.IpfsNode, mux *http.ServeMux) (*http.ServeMux, error) { +func defaultMux(path string) corehttp.ServeOption { + return func(node *core.IpfsNode, _ net.Listener, mux *http.ServeMux) (*http.ServeMux, error) { mux.Handle(path, http.DefaultServeMux) return mux, nil } diff --git a/commands/http/handler.go b/commands/http/handler.go index 53f63517f09..03ef3909917 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 4539d16415e..b61a4145763 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 0a65fcc3a72..d96818e2a34 100644 --- a/core/corehttp/commands.go +++ b/core/corehttp/commands.go @@ -1,8 +1,10 @@ package corehttp import ( + "net" "net/http" "os" + "strconv" "strings" cors "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/rs/cors" @@ -28,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 != "" { @@ -57,8 +66,41 @@ 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, mux *http.ServeMux) (*http.ServeMux, error) { + return func(n *core.IpfsNode, l net.Listener, mux *http.ServeMux) (*http.ServeMux, error) { cfg := &cmdsHttp.ServerConfig{ CORSOpts: &cors.Options{ @@ -68,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) diff --git a/core/corehttp/corehttp.go b/core/corehttp/corehttp.go index 042f056ad24..dc221f3cdf4 100644 --- a/core/corehttp/corehttp.go +++ b/core/corehttp/corehttp.go @@ -23,16 +23,16 @@ var log = eventlog.Logger("core/server") // It returns the mux to expose to future options, which may be a new mux if it // is interested in mediating requests to future options, or the same mux // initially passed in if not. -type ServeOption func(*core.IpfsNode, *http.ServeMux) (*http.ServeMux, error) +type ServeOption func(*core.IpfsNode, net.Listener, *http.ServeMux) (*http.ServeMux, error) // makeHandler turns a list of ServeOptions into a http.Handler that implements // all of the given options, in order. -func makeHandler(n *core.IpfsNode, options ...ServeOption) (http.Handler, error) { +func makeHandler(n *core.IpfsNode, l net.Listener, options ...ServeOption) (http.Handler, error) { topMux := http.NewServeMux() mux := topMux for _, option := range options { var err error - mux, err = option(n, mux) + mux, err = option(n, l, mux) if err != nil { return nil, err } @@ -65,7 +65,7 @@ func ListenAndServe(n *core.IpfsNode, listeningMultiAddr string, options ...Serv } func Serve(node *core.IpfsNode, lis net.Listener, options ...ServeOption) error { - handler, err := makeHandler(node, options...) + handler, err := makeHandler(node, lis, options...) if err != nil { return err } diff --git a/core/corehttp/gateway.go b/core/corehttp/gateway.go index f70a1d11fbf..584a894376a 100644 --- a/core/corehttp/gateway.go +++ b/core/corehttp/gateway.go @@ -2,6 +2,7 @@ package corehttp import ( "fmt" + "net" "net/http" "sync" @@ -27,7 +28,7 @@ func NewGateway(conf GatewayConfig) *Gateway { } func (g *Gateway) ServeOption() ServeOption { - return func(n *core.IpfsNode, mux *http.ServeMux) (*http.ServeMux, error) { + return func(n *core.IpfsNode, _ net.Listener, mux *http.ServeMux) (*http.ServeMux, error) { // pass user's HTTP headers g.Config.Headers = n.Repo.Config().Gateway.HTTPHeaders @@ -50,7 +51,7 @@ func GatewayOption(writable bool) ServeOption { } func VersionOption() ServeOption { - return func(n *core.IpfsNode, mux *http.ServeMux) (*http.ServeMux, error) { + return func(n *core.IpfsNode, _ net.Listener, mux *http.ServeMux) (*http.ServeMux, error) { mux.HandleFunc("/version", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Client Version: %s\n", id.ClientVersion) fmt.Fprintf(w, "Protocol Version: %s\n", id.IpfsVersion) diff --git a/core/corehttp/gateway_test.go b/core/corehttp/gateway_test.go index 01d4295b719..7ad3584da57 100644 --- a/core/corehttp/gateway_test.go +++ b/core/corehttp/gateway_test.go @@ -55,6 +55,14 @@ func newNodeWithMockNamesys(t *testing.T, ns mockNamesys) *core.IpfsNode { return n } +type delegatedHandler struct { + http.Handler +} + +func (dh *delegatedHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + dh.Handler.ServeHTTP(w, r) +} + func TestGatewayGet(t *testing.T) { t.Skip("not sure whats going on here") ns := mockNamesys{} @@ -65,7 +73,14 @@ func TestGatewayGet(t *testing.T) { } ns["example.com"] = path.FromString("/ipfs/" + k) - h, err := makeHandler(n, + // need this variable here since we need to construct handler with + // listener, and server with handler. yay cycles. + dh := &delegatedHandler{} + ts := httptest.NewServer(dh) + defer ts.Close() + + dh.Handler, err = makeHandler(n, + ts.Listener, IPNSHostnameOption(), GatewayOption(false), ) @@ -73,9 +88,6 @@ func TestGatewayGet(t *testing.T) { t.Fatal(err) } - ts := httptest.NewServer(h) - defer ts.Close() - t.Log(ts.URL) for _, test := range []struct { host string diff --git a/core/corehttp/ipns_hostname.go b/core/corehttp/ipns_hostname.go index 6f31e526884..10edb0aced3 100644 --- a/core/corehttp/ipns_hostname.go +++ b/core/corehttp/ipns_hostname.go @@ -1,6 +1,7 @@ package corehttp import ( + "net" "net/http" "strings" @@ -13,7 +14,7 @@ import ( // an IPNS name. // The rewritten request points at the resolved name on the gateway handler. func IPNSHostnameOption() ServeOption { - return func(n *core.IpfsNode, mux *http.ServeMux) (*http.ServeMux, error) { + return func(n *core.IpfsNode, _ net.Listener, mux *http.ServeMux) (*http.ServeMux, error) { childMux := http.NewServeMux() mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { ctx, cancel := context.WithCancel(n.Context()) diff --git a/core/corehttp/logs.go b/core/corehttp/logs.go index 7624644cf56..59b87b9bcb7 100644 --- a/core/corehttp/logs.go +++ b/core/corehttp/logs.go @@ -2,6 +2,7 @@ package corehttp import ( "io" + "net" "net/http" core "github.com/ipfs/go-ipfs/core" @@ -36,7 +37,7 @@ func (w *writeErrNotifier) Write(b []byte) (int, error) { } func LogOption() ServeOption { - return func(n *core.IpfsNode, mux *http.ServeMux) (*http.ServeMux, error) { + return func(n *core.IpfsNode, _ net.Listener, mux *http.ServeMux) (*http.ServeMux, error) { mux.HandleFunc("/logs", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(200) wnf, errs := newWriteErrNotifier(w) diff --git a/core/corehttp/prometheus.go b/core/corehttp/prometheus.go index d6e8ef4d099..0642c04b512 100644 --- a/core/corehttp/prometheus.go +++ b/core/corehttp/prometheus.go @@ -1,6 +1,7 @@ package corehttp import ( + "net" "net/http" prom "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/prometheus/client_golang/prometheus" @@ -9,7 +10,7 @@ import ( ) func PrometheusOption(path string) ServeOption { - return func(n *core.IpfsNode, mux *http.ServeMux) (*http.ServeMux, error) { + return func(n *core.IpfsNode, _ net.Listener, mux *http.ServeMux) (*http.ServeMux, error) { mux.Handle(path, prom.Handler()) return mux, nil } diff --git a/core/corehttp/redirect.go b/core/corehttp/redirect.go index 67d6c07739b..ec70ffaf911 100644 --- a/core/corehttp/redirect.go +++ b/core/corehttp/redirect.go @@ -1,6 +1,7 @@ package corehttp import ( + "net" "net/http" core "github.com/ipfs/go-ipfs/core" @@ -8,7 +9,7 @@ import ( func RedirectOption(path string, redirect string) ServeOption { handler := &redirectHandler{redirect} - return func(n *core.IpfsNode, mux *http.ServeMux) (*http.ServeMux, error) { + return func(n *core.IpfsNode, _ net.Listener, mux *http.ServeMux) (*http.ServeMux, error) { mux.Handle("/"+path+"/", handler) return mux, nil }