diff --git a/core/corehttp/gateway_handler.go b/core/corehttp/gateway_handler.go index 570e2c999..6140c17e3 100644 --- a/core/corehttp/gateway_handler.go +++ b/core/corehttp/gateway_handler.go @@ -1,6 +1,7 @@ package corehttp import ( + "fmt" "html/template" "io" "net/http" @@ -17,6 +18,7 @@ import ( dag "github.com/jbenet/go-ipfs/merkledag" path "github.com/jbenet/go-ipfs/path" "github.com/jbenet/go-ipfs/routing" + ufs "github.com/jbenet/go-ipfs/unixfs" uio "github.com/jbenet/go-ipfs/unixfs/io" u "github.com/jbenet/go-ipfs/util" ) @@ -101,6 +103,10 @@ func (i *gatewayHandler) NewDagFromReader(r io.Reader) (*dag.Node, error) { r, i.node.DAG, i.node.Pinning.GetManual(), chunk.DefaultSplitter) } +func NewDagEmptyDir() *dag.Node { + return &dag.Node{Data: ufs.FolderPBData()} +} + func (i *gatewayHandler) AddNodeToDAG(nd *dag.Node) (u.Key, error) { return i.node.DAG.Add(nd) } @@ -110,6 +116,33 @@ func (i *gatewayHandler) NewDagReader(nd *dag.Node) (uio.ReadSeekCloser, error) } func (i *gatewayHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + if r.Method == "POST" { + i.postHandler(w, r) + return + } + + if r.Method == "PUT" { + i.putHandler(w, r) + return + } + + if r.Method == "DELETE" { + i.deleteHandler(w, r) + return + } + + if r.Method == "GET" { + i.getHandler(w, r) + return + } + + errmsg := "Method " + r.Method + " not allowed: " + "bad request for " + r.URL.Path + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(errmsg)) + log.Error(errmsg) +} + +func (i *gatewayHandler) getHandler(w http.ResponseWriter, r *http.Request) { ctx, cancel := context.WithCancel(i.node.Context()) defer cancel() @@ -209,23 +242,180 @@ func (i *gatewayHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { func (i *gatewayHandler) postHandler(w http.ResponseWriter, r *http.Request) { nd, err := i.NewDagFromReader(r.Body) if err != nil { - w.WriteHeader(http.StatusInternalServerError) - log.Error(err) - w.Write([]byte(err.Error())) + internalWebError(w, err) return } k, err := i.AddNodeToDAG(nd) if err != nil { - w.WriteHeader(http.StatusInternalServerError) - log.Error(err) - w.Write([]byte(err.Error())) + internalWebError(w, err) return } - //TODO: return json representation of list instead - w.WriteHeader(http.StatusCreated) - w.Write([]byte(mh.Multihash(k).B58String())) + h := mh.Multihash(k).B58String() + w.Header().Set("IPFS-Hash", h) + http.Redirect(w, r, IpfsPathPrefix+h, http.StatusCreated) +} + +func (i *gatewayHandler) putEmptyDirHandler(w http.ResponseWriter, r *http.Request) { + newnode := NewDagEmptyDir() + + key, err := i.node.DAG.Add(newnode) + if err != nil { + webError(w, "Could not recursively add new node", err, http.StatusInternalServerError) + return + } + + w.Header().Set("IPFS-Hash", key.String()) + http.Redirect(w, r, IpfsPathPrefix+key.String()+"/", http.StatusCreated) +} + +func (i *gatewayHandler) putHandler(w http.ResponseWriter, r *http.Request) { + urlPath := r.URL.Path + pathext := urlPath[5:] + var err error + if urlPath == IpfsPathPrefix + "QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn/" { + i.putEmptyDirHandler(w, r) + return + } + + var newnode *dag.Node + if pathext[len(pathext)-1] == '/' { + newnode = NewDagEmptyDir() + } else { + newnode, err = i.NewDagFromReader(r.Body) + if err != nil { + webError(w, "Could not create DAG from request", err, http.StatusInternalServerError) + return + } + } + + h, components, err := path.SplitAbsPath(path.Path(urlPath)) + if err != nil { + webError(w, "Could not split path", err, http.StatusInternalServerError) + return + } + + if len(components) < 1 { + err = fmt.Errorf("Cannot override existing object") + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(err.Error())) + log.Error("%s", err) + return + } + + rootnd, err := i.node.Resolver.DAG.Get(u.Key(h)) + if err != nil { + webError(w, "Could not resolve root object", err, http.StatusBadRequest) + return + } + + // resolving path components into merkledag nodes. if a component does not + // resolve, create empty directories (which will be linked and populated below.) + path_nodes, err := i.node.Resolver.ResolveLinks(rootnd, components[:len(components)-1]) + if _, ok := err.(path.ErrNoLink); ok { + // Create empty directories, links will be made further down the code + for len(path_nodes) < len(components) { + path_nodes = append(path_nodes, NewDagEmptyDir()) + } + } else if err != nil { + webError(w, "Could not resolve parent object", err, http.StatusBadRequest) + return + } + + for i := len(path_nodes) - 1; i >= 0; i-- { + newnode, err = path_nodes[i].UpdateNodeLink(components[i], newnode) + if err != nil { + webError(w, "Could not update node links", err, http.StatusInternalServerError) + return + } + } + + err = i.node.DAG.AddRecursive(newnode) + if err != nil { + webError(w, "Could not add recursively new node", err, http.StatusInternalServerError) + return + } + + // Redirect to new path + key, err := newnode.Key() + if err != nil { + webError(w, "Could not get key of new node", err, http.StatusInternalServerError) + return + } + + w.Header().Set("IPFS-Hash", key.String()) + http.Redirect(w, r, IpfsPathPrefix+key.String()+"/"+strings.Join(components, "/"), http.StatusCreated) +} + +func (i *gatewayHandler) deleteHandler(w http.ResponseWriter, r *http.Request) { + urlPath := r.URL.Path + h, components, err := path.SplitAbsPath(path.Path(urlPath)) + if err != nil { + webError(w, "Could not split path", err, http.StatusInternalServerError) + return + } + + rootnd, err := i.node.Resolver.DAG.Get(u.Key(h)) + if err != nil { + webError(w, "Could not resolve root object", err, http.StatusBadRequest) + return + } + + path_nodes, err := i.node.Resolver.ResolveLinks(rootnd, components[:len(components)-1]) + if err != nil { + webError(w, "Could not resolve parent object", err, http.StatusBadRequest) + return + } + + err = path_nodes[len(path_nodes)-1].RemoveNodeLink(components[len(components)-1]) + if err != nil { + webError(w, "Could not delete link", err, http.StatusBadRequest) + return + } + + newnode := path_nodes[len(path_nodes)-1] + for i := len(path_nodes) - 2; i >= 0; i-- { + newnode, err = path_nodes[i].UpdateNodeLink(components[i], newnode) + if err != nil { + webError(w, "Could not update node links", err, http.StatusInternalServerError) + return + } + } + + err = i.node.DAG.AddRecursive(newnode) + if err != nil { + webError(w, "Could not add recursively new node", err, http.StatusInternalServerError) + return + } + + // Redirect to new path + key, err := newnode.Key() + if err != nil { + webError(w, "Could not get key of new node", err, http.StatusInternalServerError) + return + } + + w.Header().Set("IPFS-Hash", key.String()) + http.Redirect(w, r, IpfsPathPrefix+key.String()+"/"+strings.Join(components[:len(components)-1], "/"), http.StatusCreated) +} + +func webError(w http.ResponseWriter, message string, err error, defaultCode int) { + if _, ok := err.(path.ErrNoLink); ok { + webErrorWithCode(w, message, err, http.StatusNotFound) + } else if err == routing.ErrNotFound { + webErrorWithCode(w, message, err, http.StatusNotFound) + } else if err == context.DeadlineExceeded { + webErrorWithCode(w, message, err, http.StatusRequestTimeout) + } else { + webErrorWithCode(w, message, err, defaultCode) + } +} + +func webErrorWithCode(w http.ResponseWriter, message string, err error, code int) { + w.WriteHeader(code) + log.Errorf("%s: %s", message, err) + w.Write([]byte(message + ": " + err.Error())) } // return a 500 error and log diff --git a/fuse/ipns/ipns_unix.go b/fuse/ipns/ipns_unix.go index 57c10688a..63eed45e1 100644 --- a/fuse/ipns/ipns_unix.go +++ b/fuse/ipns/ipns_unix.go @@ -292,13 +292,13 @@ func (s *Node) Attr() fuse.Attr { // Lookup performs a lookup under this node. func (s *Node) Lookup(name string, intr fs.Intr) (fs.Node, fuse.Error) { log.Debugf("ipns: node[%s] Lookup '%s'", s.name, name) - nd, err := s.Ipfs.Resolver.ResolveLinks(s.Nd, []string{name}) + nodes, err := s.Ipfs.Resolver.ResolveLinks(s.Nd, []string{name}) if err != nil { // todo: make this error more versatile. return nil, fuse.ENOENT } - return s.makeChild(name, nd), nil + return s.makeChild(name, nodes[len(nodes)-1]), nil } func (n *Node) makeChild(name string, node *mdag.Node) *Node { @@ -650,12 +650,11 @@ func (n *Node) Rename(req *fuse.RenameRequest, newDir fs.Node, intr fs.Intr) fus // Updates the child of this node, specified by name to the given newnode func (n *Node) update(name string, newnode *mdag.Node) error { log.Debugf("update '%s' in '%s'", name, n.name) - nnode := n.Nd.Copy() - err := nnode.RemoveNodeLink(name) + + nnode, err := n.Nd.UpdateNodeLink(name, newnode) if err != nil { return err } - nnode.AddNodeLink(name, newnode) if n.parent != nil { err := n.parent.update(n.name, nnode) diff --git a/fuse/readonly/readonly_unix.go b/fuse/readonly/readonly_unix.go index 863c570e0..da2c3fb21 100644 --- a/fuse/readonly/readonly_unix.go +++ b/fuse/readonly/readonly_unix.go @@ -118,13 +118,13 @@ func (s *Node) Attr() fuse.Attr { // Lookup performs a lookup under this node. func (s *Node) Lookup(name string, intr fs.Intr) (fs.Node, fuse.Error) { log.Debugf("Lookup '%s'", name) - nd, err := s.Ipfs.Resolver.ResolveLinks(s.Nd, []string{name}) + nodes, err := s.Ipfs.Resolver.ResolveLinks(s.Nd, []string{name}) if err != nil { // todo: make this error more versatile. return nil, fuse.ENOENT } - return &Node{Ipfs: s.Ipfs, Nd: nd}, nil + return &Node{Ipfs: s.Ipfs, Nd: nodes[len(nodes)-1]}, nil } // ReadDir reads the link structure as directory entries diff --git a/merkledag/node.go b/merkledag/node.go index 016f4353b..637563290 100644 --- a/merkledag/node.go +++ b/merkledag/node.go @@ -134,6 +134,16 @@ func (n *Node) Copy() *Node { return nnode } +// UpdateNodeLink return a copy of the node with the link name set to point to +// that. If a link of the same name existed, it is removed. +func (n *Node) UpdateNodeLink(name string, that *Node) (*Node, error) { + newnode := n.Copy() + err := newnode.RemoveNodeLink(name) + err = nil // ignore error + err = newnode.AddNodeLink(name, that) + return newnode, err +} + // Size returns the total size of the data addressed by node, // including the total sizes of references. func (n *Node) Size() (uint64, error) { diff --git a/path/resolver.go b/path/resolver.go index 6c9366124..863ae9d3c 100644 --- a/path/resolver.go +++ b/path/resolver.go @@ -11,16 +11,26 @@ import ( var log = u.Logger("path") +// ErrNoLink is returned when a link is not found in a path +type ErrNoLink struct { + name string + node mh.Multihash +} + +func (e ErrNoLink) Error() string { + return fmt.Sprintf("no link named %q under %s", e.name, e.node.B58String()) +} + // Resolver provides path resolution to IPFS // It has a pointer to a DAGService, which is uses to resolve nodes. type Resolver struct { DAG merkledag.DAGService } -// ResolvePath fetches the node for given path. It uses the first -// path component as a hash (key) of the first node, then resolves -// all other components walking the links, with ResolveLinks. -func (s *Resolver) ResolvePath(fpath Path) (*merkledag.Node, error) { +// SplitAbsPath clean up and split fpath. It extracts the first component (which +// must be a Multihash) and return it separately. +func SplitAbsPath(fpath Path) (mh.Multihash, []string, error) { + log.Debugf("Resolve: '%s'", fpath) parts := fpath.Segments() @@ -30,13 +40,36 @@ func (s *Resolver) ResolvePath(fpath Path) (*merkledag.Node, error) { // if nothing, bail. if len(parts) == 0 { - return nil, fmt.Errorf("ipfs path must contain at least one component") + return nil, nil, fmt.Errorf("ipfs path must contain at least one component") } // first element in the path is a b58 hash (for now) h, err := mh.FromB58String(parts[0]) if err != nil { log.Debug("given path element is not a base58 string.\n") + return nil, nil, err + } + + return h, parts[1:], nil +} + +// ResolvePath fetches the node for given path. It returns the last item +// returned by ResolvePathComponents. +func (s *Resolver) ResolvePath(fpath Path) (*merkledag.Node, error) { + nodes, err := s.ResolvePathComponents(fpath) + if err != nil || nodes == nil { + return nil, err + } else { + return nodes[len(nodes)-1], err + } +} + +// ResolvePathComponents fetches the nodes for each segment of the given path. +// It uses the first path component as a hash (key) of the first node, then +// resolves all other components walking the links, with ResolveLinks. +func (s *Resolver) ResolvePathComponents(fpath Path) ([]*merkledag.Node, error) { + h, parts, err := SplitAbsPath(fpath) + if err != nil { return nil, err } @@ -46,19 +79,22 @@ func (s *Resolver) ResolvePath(fpath Path) (*merkledag.Node, error) { return nil, err } - return s.ResolveLinks(nd, parts[1:]) + return s.ResolveLinks(nd, parts) } // ResolveLinks iteratively resolves names by walking the link hierarchy. // Every node is fetched from the DAGService, resolving the next name. -// Returns the last node found. +// Returns the list of nodes forming the path, starting with ndd. This list is +// guaranteed never to be empty. // // ResolveLinks(nd, []string{"foo", "bar", "baz"}) // would retrieve "baz" in ("bar" in ("foo" in nd.Links).Links).Links func (s *Resolver) ResolveLinks(ndd *merkledag.Node, names []string) ( - nd *merkledag.Node, err error) { + result []*merkledag.Node, err error) { - nd = ndd // dup arg workaround + result = make([]*merkledag.Node, 0, len(names)+1) + result = append(result, ndd) + nd := ndd // dup arg workaround // for each of the path components for _, name := range names { @@ -75,21 +111,22 @@ func (s *Resolver) ResolveLinks(ndd *merkledag.Node, names []string) ( } if next == "" { - h1, _ := nd.Multihash() - h2 := h1.B58String() - return nil, fmt.Errorf("no link named %q under %s", name, h2) + n, _ := nd.Multihash() + return result, ErrNoLink{name: name, node: n} } if nlink.Node == nil { // fetch object for link and assign to nd nd, err = s.DAG.Get(next) if err != nil { - return nd, err + return append(result, nd), err } nlink.Node = nd } else { nd = nlink.Node } + + result = append(result, nlink.Node) } return } diff --git a/util/util.go b/util/util.go index 611c988ed..a96235859 100644 --- a/util/util.go +++ b/util/util.go @@ -144,3 +144,19 @@ func (m MultiErr) Error() string { } return s } + +func Partition(subject string, sep string) (string, string, string) { + if i := strings.Index(subject, sep); i != -1 { + return subject[:i], subject[i : i+len(sep)], subject[i+len(sep):] + } else { + return subject, "", "" + } +} + +func RPartition(subject string, sep string) (string, string, string) { + if i := strings.LastIndex(subject, sep); i != -1 { + return subject[:i], subject[i : i+len(sep)], subject[i+len(sep):] + } else { + return subject, "", "" + } +}