diff --git a/cmd/ipfs/daemon.go b/cmd/ipfs/daemon.go index 94adb527a..d04ffdff4 100644 --- a/cmd/ipfs/daemon.go +++ b/cmd/ipfs/daemon.go @@ -3,6 +3,7 @@ package main import ( "fmt" "os" + "strings" ma "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr" cmds "github.com/jbenet/go-ipfs/commands" @@ -192,10 +193,21 @@ func daemonFunc(req cmds.Request, res cmds.Response) { }() } + blocklist := &corehttp.BlockList{} + blocklist.SetDecider(func(s string) bool { + // only allow paths that begin with the WebUI path + return strings.HasPrefix(s, corehttp.WebUIPath) + }) + gatewayConfig := corehttp.GatewayConfig{ + Writable: true, + BlockList: blocklist, + } + gatewayOption := corehttp.NewGateway(gatewayConfig).ServeOption() + var opts = []corehttp.ServeOption{ corehttp.CommandsOption(*req.Context()), corehttp.WebUIOption, - corehttp.GatewayOption(true), + gatewayOption, } if rootRedirect != nil { opts = append(opts, rootRedirect) diff --git a/commands/files/multipartfile.go b/commands/files/multipartfile.go index 4594b859b..844e0afa9 100644 --- a/commands/files/multipartfile.go +++ b/commands/files/multipartfile.go @@ -4,6 +4,7 @@ import ( "mime" "mime/multipart" "net/http" + "net/url" ) const ( @@ -67,7 +68,12 @@ func (f *MultipartFile) NextFile() (File, error) { } func (f *MultipartFile) FileName() string { - return f.Part.FileName() + filename, err := url.QueryUnescape(f.Part.FileName()) + if err != nil { + // if there is a unescape error, just treat the name as unescaped + return f.Part.FileName() + } + return filename } func (f *MultipartFile) Read(p []byte) (int, error) { diff --git a/commands/http/handler.go b/commands/http/handler.go index 999135a76..58e90370d 100644 --- a/commands/http/handler.go +++ b/commands/http/handler.go @@ -6,6 +6,7 @@ import ( "io" "net/http" "strconv" + "strings" context "github.com/jbenet/go-ipfs/Godeps/_workspace/src/code.google.com/p/go.net/context" @@ -55,6 +56,20 @@ func (i Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { log.Debug("Incoming API request: ", r.URL) + // error on external referers (to prevent CSRF attacks) + referer := r.Referer() + scheme := r.URL.Scheme + if len(scheme) == 0 { + scheme = "http" + } + host := fmt.Sprintf("%s://%s/", scheme, r.Host) + // empty string means the user isn't following a link (they are directly typing in the url) + if referer != "" && !strings.HasPrefix(referer, host) { + w.WriteHeader(http.StatusForbidden) + w.Write([]byte("403 - Forbidden")) + return + } + if len(i.origin) > 0 { w.Header().Set("Access-Control-Allow-Origin", i.origin) } diff --git a/commands/http/multifilereader.go b/commands/http/multifilereader.go index 37c26dca3..0872b2ee6 100644 --- a/commands/http/multifilereader.go +++ b/commands/http/multifilereader.go @@ -6,6 +6,7 @@ import ( "io" "mime/multipart" "net/textproto" + "net/url" "sync" files "github.com/jbenet/go-ipfs/commands/files" @@ -74,11 +75,12 @@ func (mfr *MultiFileReader) Read(buf []byte) (written int, err error) { // write the boundary and headers header := make(textproto.MIMEHeader) + filename := url.QueryEscape(file.FileName()) if mfr.form { - contentDisposition := fmt.Sprintf("form-data; name=\"file\"; filename=\"%s\"", file.FileName()) + contentDisposition := fmt.Sprintf("form-data; name=\"file\"; filename=\"%s\"", filename) header.Set("Content-Disposition", contentDisposition) } else { - header.Set("Content-Disposition", fmt.Sprintf("file; filename=\"%s\"", file.FileName())) + header.Set("Content-Disposition", fmt.Sprintf("file; filename=\"%s\"", filename)) } if file.IsDirectory() { diff --git a/core/corehttp/gateway.go b/core/corehttp/gateway.go index 0e0601b34..ae0b45c6c 100644 --- a/core/corehttp/gateway.go +++ b/core/corehttp/gateway.go @@ -47,7 +47,6 @@ func GatewayOption(writable bool) ServeOption { type Decider func(string) bool type BlockList struct { - mu sync.RWMutex d Decider } diff --git a/core/corehttp/gateway_handler.go b/core/corehttp/gateway_handler.go index 93c33cb2e..6c5f70818 100644 --- a/core/corehttp/gateway_handler.go +++ b/core/corehttp/gateway_handler.go @@ -48,15 +48,15 @@ type directoryItem struct { // gatewayHandler is a HTTP handler that serves IPFS objects (accessible by default at /ipfs/) // (it serves requests like GET /ipfs/QmVRzPKPzNtSrEzBFm2UZfxmPAgnaLke4DMcerbsGGSaFe/link) type gatewayHandler struct { - node *core.IpfsNode - dirList *template.Template - config GatewayConfig + node *core.IpfsNode + dirList *template.Template + config GatewayConfig } func newGatewayHandler(node *core.IpfsNode, conf GatewayConfig) (*gatewayHandler, error) { i := &gatewayHandler{ - node: node, - config: conf, + node: node, + config: conf, } err := i.loadTemplate() if err != nil { @@ -167,7 +167,8 @@ func (i *gatewayHandler) getHandler(w http.ResponseWriter, r *http.Request) { urlPath := r.URL.Path if i.config.BlockList != nil && i.config.BlockList.ShouldBlock(urlPath) { - w.WriteHeader(http.StatusNotFound) + w.WriteHeader(http.StatusForbidden) + w.Write([]byte("403 - Forbidden")) return } @@ -193,6 +194,12 @@ func (i *gatewayHandler) getHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("X-IPFS-Path", p) + // Suborigin header, sandboxes apps from each other in the browser (even + // though they are served from the same gateway domain). NOTE: This is not + // yet widely supported by browsers. + pathRoot := strings.SplitN(urlPath, "/", 4)[2] + w.Header().Set("Suborigin", pathRoot) + dr, err := i.NewDagReader(nd) if err != nil && err != uio.ErrIsDir { // not a directory and still an error diff --git a/core/corehttp/webui.go b/core/corehttp/webui.go index 46056496b..18f5f2eb0 100644 --- a/core/corehttp/webui.go +++ b/core/corehttp/webui.go @@ -1,6 +1,6 @@ package corehttp // TODO: move to IPNS -const webuiPath = "/ipfs/QmctngrQAt9fjpQUZr7Bx3BsXUcif52eZGTizWhvcShsjz" +const WebUIPath = "/ipfs/QmSHDxWsMPuJQKWmVA1rB5a3NX2Eme5fPqNb63qwaqiqSp" -var WebUIOption = RedirectOption("webui", webuiPath) +var WebUIOption = RedirectOption("webui", WebUIPath) diff --git a/repo/config/init.go b/repo/config/init.go index 0efcf629a..49d80c781 100644 --- a/repo/config/init.go +++ b/repo/config/init.go @@ -35,7 +35,8 @@ func Init(out io.Writer, nBitsForKeypair int) (*Config, error) { "/ip4/0.0.0.0/tcp/4001", // "/ip4/0.0.0.0/udp/4002/utp", // disabled for now. }, - API: "/ip4/127.0.0.1/tcp/5001", + API: "/ip4/127.0.0.1/tcp/5001", + Gateway: "/ip4/127.0.0.1/tcp/8080", }, Bootstrap: BootstrapPeerStrings(bootstrapPeers), diff --git a/test/sharness/t0110-gateway.sh b/test/sharness/t0110-gateway.sh index 628f51273..f833f85b6 100755 --- a/test/sharness/t0110-gateway.sh +++ b/test/sharness/t0110-gateway.sh @@ -22,7 +22,7 @@ test_launch_ipfs_daemon test_expect_success "GET IPFS path succeeds" ' echo "Hello Worlds!" > expected && HASH=`ipfs add -q expected` && - wget "http://127.0.0.1:5001/ipfs/$HASH" -O actual + wget "http://127.0.0.1:5002/ipfs/$HASH" -O actual ' test_expect_success "GET IPFS path output looks good" ' @@ -34,11 +34,11 @@ test_expect_success "GET IPFS directory path succeeds" ' mkdir dir && echo "12345" > dir/test && HASH2=`ipfs add -r -q dir | tail -n 1` && - wget "http://127.0.0.1:5001/ipfs/$HASH2" + wget "http://127.0.0.1:5002/ipfs/$HASH2" ' test_expect_success "GET IPFS directory file succeeds" ' - wget "http://127.0.0.1:5001/ipfs/$HASH2/test" -O actual + wget "http://127.0.0.1:5002/ipfs/$HASH2/test" -O actual ' test_expect_success "GET IPFS directory file output looks good" ' @@ -48,7 +48,7 @@ test_expect_success "GET IPFS directory file output looks good" ' test_expect_failure "GET IPNS path succeeds" ' ipfs name publish "$HASH" && NAME=`ipfs config Identity.PeerID` && - wget "http://127.0.0.1:5001/ipns/$NAME" -O actual + wget "http://127.0.0.1:5002/ipns/$NAME" -O actual ' test_expect_failure "GET IPNS path output looks good" ' @@ -56,11 +56,11 @@ test_expect_failure "GET IPNS path output looks good" ' ' test_expect_success "GET invalid IPFS path errors" ' - test_must_fail wget http://127.0.0.1:5001/ipfs/12345 + test_must_fail wget http://127.0.0.1:5002/ipfs/12345 ' test_expect_success "GET invalid path errors" ' - test_must_fail wget http://127.0.0.1:5001/12345 + test_must_fail wget http://127.0.0.1:5002/12345 ' test_kill_ipfs_daemon