// ParseMultiaddr parses a multiaddr into an IPFSAddr func ParseMultiaddr(m ma.Multiaddr) (a IPFSAddr, err error) { // never panic. defer func() { if r := recover(); r != nil { log.Debug("recovered from panic: ", r) a = nil err = ErrInvalidAddr } }() if m == nil { return nil, ErrInvalidAddr } // make sure it's an ipfs addr parts := ma.Split(m) if len(parts) < 1 { return nil, ErrInvalidAddr } ipfspart := parts[len(parts)-1] // last part if ipfspart.Protocols()[0].Code != ma.P_IPFS { return nil, ErrInvalidAddr } // make sure ipfs id parses as a peer.ID peerIdParts := path.SplitList(ipfspart.String()) peerIdStr := peerIdParts[len(peerIdParts)-1] id, err := peer.IDB58Decode(peerIdStr) if err != nil { return nil, err } return ipfsAddr{ma: m, id: id}, nil }
func mkdirP(t *testing.T, root *Directory, pth string) *Directory { dirs := path.SplitList(pth) cur := root for _, d := range dirs { n, err := cur.Mkdir(d) if err != nil && err != os.ErrExist { t.Fatal(err) } if err == os.ErrExist { fsn, err := cur.Child(d) if err != nil { t.Fatal(err) } switch fsn := fsn.(type) { case *Directory: n = fsn case *File: t.Fatal("tried to make a directory where a file already exists") } } cur = n } return cur }
// adds a '-' to the beginning of each path element so we can use 'data' as a // special link in the structure without having to worry about func escapePath(pth string) string { elems := path.SplitList(strings.Trim(pth, "/")) for i, e := range elems { elems[i] = "-" + e } return path.Join(elems) }
// Mkdir creates a directory at 'path' under the directory 'd', creating // intermediary directories as needed if 'mkparents' is set to true func Mkdir(r *Root, pth string, mkparents bool, flush bool) error { if pth == "" { return fmt.Errorf("no path given to Mkdir") } parts := path.SplitList(pth) if parts[0] == "" { parts = parts[1:] } // allow 'mkdir /a/b/c/' to create c if parts[len(parts)-1] == "" { parts = parts[:len(parts)-1] } if len(parts) == 0 { // this will only happen on 'mkdir /' if mkparents { return nil } return fmt.Errorf("cannot create directory '/': Already exists") } cur := r.GetValue().(*Directory) for i, d := range parts[:len(parts)-1] { fsn, err := cur.Child(d) if err == os.ErrNotExist && mkparents { mkd, err := cur.Mkdir(d) if err != nil { return err } fsn = mkd } else if err != nil { return err } next, ok := fsn.(*Directory) if !ok { return fmt.Errorf("%s was not a directory", path.Join(parts[:i])) } cur = next } final, err := cur.Mkdir(parts[len(parts)-1]) if err != nil { if !mkparents || err != os.ErrExist || final == nil { return err } } if flush { err := final.Flush() if err != nil { return err } } return nil }
func (e *Editor) RmLink(ctx context.Context, pth string) error { splpath := path.SplitList(pth) nd, err := e.rmLink(ctx, e.root, splpath) if err != nil { return err } e.root = nd return nil }
func (e *Editor) InsertNodeAtPath(ctx context.Context, pth string, toinsert *dag.Node, create func() *dag.Node) error { splpath := path.SplitList(pth) nd, err := e.insertNodeAtPath(ctx, e.root, splpath, toinsert, create) if err != nil { return err } e.root = nd return nil }
func escapeDhtKey(s string) (string, error) { parts := path.SplitList(s) switch len(parts) { case 1: return string(b58.Decode(s)), nil case 3: k := b58.Decode(parts[2]) return path.Join(append(parts[:2], string(k))), nil default: return "", errors.New("invalid key") } }
func escapeDhtKey(s string) (key.Key, error) { parts := path.SplitList(s) switch len(parts) { case 1: return key.B58KeyDecode(s), nil case 3: k := key.B58KeyDecode(parts[2]) return key.Key(path.Join(append(parts[:2], k.String()))), nil default: return "", errors.New("invalid key") } }
func assertFileAtPath(ds dag.DAGService, root *Directory, expn node.Node, pth string) error { exp, ok := expn.(*dag.ProtoNode) if !ok { return dag.ErrNotProtobuf } parts := path.SplitList(pth) cur := root for i, d := range parts[:len(parts)-1] { next, err := cur.Child(d) if err != nil { return fmt.Errorf("looking for %s failed: %s", pth, err) } nextDir, ok := next.(*Directory) if !ok { return fmt.Errorf("%s points to a non-directory", parts[:i+1]) } cur = nextDir } last := parts[len(parts)-1] finaln, err := cur.Child(last) if err != nil { return err } file, ok := finaln.(*File) if !ok { return fmt.Errorf("%s was not a file!", pth) } rfd, err := file.Open(OpenReadOnly, false) if err != nil { return err } out, err := ioutil.ReadAll(rfd) if err != nil { return err } expbytes, err := catNode(ds, exp) if err != nil { return err } if !bytes.Equal(out, expbytes) { return fmt.Errorf("Incorrect data at path!") } return nil }
func (v Validator) IsSigned(k key.Key) (bool, error) { // Now, check validity func parts := path.SplitList(string(k)) if len(parts) < 3 { log.Infof("Record key does not have validator: %s", k) return false, nil } val, ok := v[parts[1]] if !ok { log.Infof("Unrecognized key prefix: %s", parts[1]) return false, ErrInvalidRecordType } return val.Sign, nil }
// VerifyRecord checks a record and ensures it is still valid. // It runs needed validators func (v Validator) VerifyRecord(r *pb.Record) error { // Now, check validity func parts := path.SplitList(r.GetKey()) if len(parts) < 3 { log.Infof("Record key does not have validator: %s", key.Key(r.GetKey())) return nil } val, ok := v[parts[1]] if !ok { log.Infof("Unrecognized key prefix: %s", parts[1]) return ErrInvalidRecordType } return val.Func(key.Key(r.GetKey()), r.GetValue()) }
func assertNodeAtPath(t *testing.T, ds dag.DAGService, root *dag.ProtoNode, pth string, exp *cid.Cid) { parts := path.SplitList(pth) cur := root for _, e := range parts { nxt, err := cur.GetLinkedProtoNode(context.Background(), ds, e) if err != nil { t.Fatal(err) } cur = nxt } curc := cur.Cid() if !curc.Equals(exp) { t.Fatal("node not as expected at end of path") } }
func (s Selector) BestRecord(k key.Key, recs [][]byte) (int, error) { if len(recs) == 0 { return 0, errors.New("no records given!") } parts := path.SplitList(string(k)) if len(parts) < 3 { log.Infof("Record key does not have selectorfunc: %s", k) return 0, errors.New("record key does not have selectorfunc") } sel, ok := s[parts[1]] if !ok { log.Infof("Unrecognized key prefix: %s", parts[1]) return 0, ErrInvalidRecordType } return sel(k, recs) }
func assertNodeAtPath(t *testing.T, ds dag.DAGService, root *dag.Node, pth string, exp key.Key) { parts := path.SplitList(pth) cur := root for _, e := range parts { nxt, err := cur.GetLinkedNode(context.Background(), ds, e) if err != nil { t.Fatal(err) } cur = nxt } curk, err := cur.Key() if err != nil { t.Fatal(err) } if curk != exp { t.Fatal("node not as expected at end of path") } }
func TestIDMatches(t *testing.T) { for _, g := range good { a, err := ParseString(g) if err != nil { t.Error("failed to parse", g, err) continue } sp := path.SplitList(g) sid := sp[len(sp)-1] id, err := peer.IDB58Decode(sid) if err != nil { t.Error("failed to parse", sid, err) continue } if a.ID() != id { t.Error("not equal", a.ID(), id) } } }
// DirLookup will look up a file or directory at the given path // under the directory 'd' func DirLookup(d *Directory, pth string) (FSNode, error) { pth = strings.Trim(pth, "/") parts := path.SplitList(pth) if len(parts) == 1 && parts[0] == "" { return d, nil } var cur FSNode cur = d for i, p := range parts { chdir, ok := cur.(*Directory) if !ok { return nil, fmt.Errorf("cannot access %s: Not a directory", path.Join(parts[:i+1])) } child, err := chdir.Child(p) if err != nil { return nil, err } cur = child } return cur, nil }
func (i *gatewayHandler) getOrHeadHandler(w http.ResponseWriter, r *http.Request) { ctx, cancel := context.WithTimeout(i.node.Context(), time.Hour) // the hour is a hard fallback, we don't expect it to happen, but just in case defer cancel() if cn, ok := w.(http.CloseNotifier); ok { clientGone := cn.CloseNotify() go func() { select { case <-clientGone: case <-ctx.Done(): } cancel() }() } urlPath := r.URL.Path // If the gateway is behind a reverse proxy and mounted at a sub-path, // the prefix header can be set to signal this sub-path. // It will be prepended to links in directory listings and the index.html redirect. prefix := "" if prefixHdr := r.Header["X-Ipfs-Gateway-Prefix"]; len(prefixHdr) > 0 { log.Debugf("X-Ipfs-Gateway-Prefix: %s", prefixHdr[0]) prefix = prefixHdr[0] } // IPNSHostnameOption might have constructed an IPNS path using the Host header. // In this case, we need the original path for constructing redirects // and links that match the requested URL. // For example, http://example.net would become /ipns/example.net, and // the redirects and links would end up as http://example.net/ipns/example.net originalUrlPath := prefix + urlPath ipnsHostname := false if hdr := r.Header["X-Ipns-Original-Path"]; len(hdr) > 0 { originalUrlPath = prefix + hdr[0] ipnsHostname = true } if i.config.BlockList != nil && i.config.BlockList.ShouldBlock(urlPath) { w.WriteHeader(http.StatusForbidden) w.Write([]byte("403 - Forbidden")) return } nd, err := core.Resolve(ctx, i.node, path.Path(urlPath)) if err != nil { webError(w, "Path Resolve error", err, http.StatusBadRequest) return } etag := gopath.Base(urlPath) if r.Header.Get("If-None-Match") == etag { w.WriteHeader(http.StatusNotModified) return } i.addUserHeaders(w) // ok, _now_ write user's headers. w.Header().Set("X-IPFS-Path", urlPath) // set 'allowed' headers w.Header().Set("Access-Control-Allow-Headers", "X-Stream-Output, X-Chunked-Output") // expose those headers w.Header().Set("Access-Control-Expose-Headers", "X-Stream-Output, X-Chunked-Output") // Suborigin header, sandboxes apps from each other in the browser (even // though they are served from the same gateway domain). // // Omited if the path was treated by IPNSHostnameOption(), for example // a request for http://example.net/ would be changed to /ipns/example.net/, // which would turn into an incorrect Suborigin: example.net header. // // NOTE: This is not yet widely supported by browsers. if !ipnsHostname { pathRoot := strings.SplitN(urlPath, "/", 4)[2] w.Header().Set("Suborigin", pathRoot) } dr, err := uio.NewDagReader(ctx, nd, i.node.DAG) if err != nil && err != uio.ErrIsDir { // not a directory and still an error internalWebError(w, err) return } // set these headers _after_ the error, for we may just not have it // and dont want the client to cache a 500 response... // and only if it's /ipfs! // TODO: break this out when we split /ipfs /ipns routes. modtime := time.Now() if strings.HasPrefix(urlPath, ipfsPathPrefix) { w.Header().Set("Etag", etag) w.Header().Set("Cache-Control", "public, max-age=29030400") // set modtime to a really long time ago, since files are immutable and should stay cached modtime = time.Unix(1, 0) } if err == nil { defer dr.Close() name := gopath.Base(urlPath) http.ServeContent(w, r, name, modtime, dr) return } // storage for directory listing var dirListing []directoryItem // loop through files foundIndex := false for _, link := range nd.Links { if link.Name == "index.html" { log.Debugf("found index.html link for %s", urlPath) foundIndex = true if urlPath[len(urlPath)-1] != '/' { // See comment above where originalUrlPath is declared. http.Redirect(w, r, originalUrlPath+"/", 302) log.Debugf("redirect to %s", originalUrlPath+"/") return } // return index page instead. nd, err := core.Resolve(ctx, i.node, path.Path(urlPath+"/index.html")) if err != nil { internalWebError(w, err) return } dr, err := uio.NewDagReader(ctx, nd, i.node.DAG) if err != nil { internalWebError(w, err) return } defer dr.Close() // write to request http.ServeContent(w, r, "index.html", modtime, dr) break } // See comment above where originalUrlPath is declared. di := directoryItem{humanize.Bytes(link.Size), link.Name, gopath.Join(originalUrlPath, link.Name)} dirListing = append(dirListing, di) } if !foundIndex { if r.Method != "HEAD" { // construct the correct back link // https://github.com/ipfs/go-ipfs/issues/1365 var backLink string = prefix + urlPath // don't go further up than /ipfs/$hash/ pathSplit := path.SplitList(backLink) switch { // keep backlink case len(pathSplit) == 3: // url: /ipfs/$hash // keep backlink case len(pathSplit) == 4 && pathSplit[3] == "": // url: /ipfs/$hash/ // add the correct link depending on wether the path ends with a slash default: if strings.HasSuffix(backLink, "/") { backLink += "./.." } else { backLink += "/.." } } // strip /ipfs/$hash from backlink if IPNSHostnameOption touched the path. if ipnsHostname { backLink = prefix + "/" if len(pathSplit) > 5 { // also strip the trailing segment, because it's a backlink backLinkParts := pathSplit[3 : len(pathSplit)-2] backLink += path.Join(backLinkParts) + "/" } } // See comment above where originalUrlPath is declared. tplData := listingTemplateData{ Listing: dirListing, Path: originalUrlPath, BackLink: backLink, } err := listingTemplate.Execute(w, tplData) if err != nil { internalWebError(w, err) return } } } }
func (i *gatewayHandler) getOrHeadHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) { urlPath := r.URL.Path // If the gateway is behind a reverse proxy and mounted at a sub-path, // the prefix header can be set to signal this sub-path. // It will be prepended to links in directory listings and the index.html redirect. prefix := "" if prefixHdr := r.Header["X-Ipfs-Gateway-Prefix"]; len(prefixHdr) > 0 { prfx := prefixHdr[0] for _, p := range i.config.PathPrefixes { if prfx == p || strings.HasPrefix(prfx, p+"/") { prefix = prfx break } } } // IPNSHostnameOption might have constructed an IPNS path using the Host header. // In this case, we need the original path for constructing redirects // and links that match the requested URL. // For example, http://example.net would become /ipns/example.net, and // the redirects and links would end up as http://example.net/ipns/example.net originalUrlPath := prefix + urlPath ipnsHostname := false if hdr := r.Header["X-Ipns-Original-Path"]; len(hdr) > 0 { originalUrlPath = prefix + hdr[0] ipnsHostname = true } dr, err := i.api.Cat(ctx, urlPath) dir := false switch err { case nil: // core.Resolve worked defer dr.Close() case coreiface.ErrIsDir: dir = true case namesys.ErrResolveFailed: // Don't log that error as it is just noise w.WriteHeader(http.StatusInternalServerError) fmt.Fprintf(w, "Path Resolve error: %s", err.Error()) log.Info("Path Resolve error: %s", err.Error()) return case coreiface.ErrOffline: if !i.node.OnlineMode() { w.WriteHeader(http.StatusServiceUnavailable) fmt.Fprint(w, "Could not resolve path. Node is in offline mode.") return } fallthrough default: // all other erros webError(w, "Path Resolve error", err, http.StatusBadRequest) return } etag := gopath.Base(urlPath) if r.Header.Get("If-None-Match") == etag { w.WriteHeader(http.StatusNotModified) return } i.addUserHeaders(w) // ok, _now_ write user's headers. w.Header().Set("X-IPFS-Path", urlPath) // set 'allowed' headers w.Header().Set("Access-Control-Allow-Headers", "X-Stream-Output, X-Chunked-Output") // expose those headers w.Header().Set("Access-Control-Expose-Headers", "X-Stream-Output, X-Chunked-Output") // Suborigin header, sandboxes apps from each other in the browser (even // though they are served from the same gateway domain). // // Omited if the path was treated by IPNSHostnameOption(), for example // a request for http://example.net/ would be changed to /ipns/example.net/, // which would turn into an incorrect Suborigin: example.net header. // // NOTE: This is not yet widely supported by browsers. if !ipnsHostname { pathRoot := strings.SplitN(urlPath, "/", 4)[2] w.Header().Set("Suborigin", pathRoot) } // set these headers _after_ the error, for we may just not have it // and dont want the client to cache a 500 response... // and only if it's /ipfs! // TODO: break this out when we split /ipfs /ipns routes. modtime := time.Now() if strings.HasPrefix(urlPath, ipfsPathPrefix) { w.Header().Set("Etag", etag) w.Header().Set("Cache-Control", "public, max-age=29030400, immutable") // set modtime to a really long time ago, since files are immutable and should stay cached modtime = time.Unix(1, 0) } if !dir { name := gopath.Base(urlPath) http.ServeContent(w, r, name, modtime, dr) return } links, err := i.api.Ls(ctx, urlPath) if err != nil { internalWebError(w, err) return } // storage for directory listing var dirListing []directoryItem // loop through files foundIndex := false for _, link := range links { if link.Name == "index.html" { log.Debugf("found index.html link for %s", urlPath) foundIndex = true if urlPath[len(urlPath)-1] != '/' { // See comment above where originalUrlPath is declared. http.Redirect(w, r, originalUrlPath+"/", 302) log.Debugf("redirect to %s", originalUrlPath+"/") return } p, err := path.ParsePath(urlPath + "/index.html") if err != nil { internalWebError(w, err) return } // return index page instead. dr, err := i.api.Cat(ctx, p.String()) if err != nil { internalWebError(w, err) return } defer dr.Close() // write to request http.ServeContent(w, r, "index.html", modtime, dr) break } // See comment above where originalUrlPath is declared. di := directoryItem{humanize.Bytes(link.Size), link.Name, gopath.Join(originalUrlPath, link.Name)} dirListing = append(dirListing, di) } if !foundIndex { if r.Method != "HEAD" { // construct the correct back link // https://github.com/ipfs/go-ipfs/issues/1365 var backLink string = prefix + urlPath // don't go further up than /ipfs/$hash/ pathSplit := path.SplitList(backLink) switch { // keep backlink case len(pathSplit) == 3: // url: /ipfs/$hash // keep backlink case len(pathSplit) == 4 && pathSplit[3] == "": // url: /ipfs/$hash/ // add the correct link depending on wether the path ends with a slash default: if strings.HasSuffix(backLink, "/") { backLink += "./.." } else { backLink += "/.." } } // strip /ipfs/$hash from backlink if IPNSHostnameOption touched the path. if ipnsHostname { backLink = prefix + "/" if len(pathSplit) > 5 { // also strip the trailing segment, because it's a backlink backLinkParts := pathSplit[3 : len(pathSplit)-2] backLink += path.Join(backLinkParts) + "/" } } // See comment above where originalUrlPath is declared. tplData := listingTemplateData{ Listing: dirListing, Path: originalUrlPath, BackLink: backLink, } err := listingTemplate.Execute(w, tplData) if err != nil { internalWebError(w, err) return } } } }
// Parse parses the data in a http.Request and returns a command Request object func Parse(r *http.Request, root *cmds.Command) (cmds.Request, error) { if !strings.HasPrefix(r.URL.Path, ApiPath) { return nil, errors.New("Unexpected path prefix") } pth := path.SplitList(strings.TrimPrefix(r.URL.Path, ApiPath+"/")) stringArgs := make([]string, 0) if err := apiVersionMatches(r); err != nil { if pth[0] != "version" { // compatibility with previous version check return nil, err } } cmd, err := root.Get(pth[:len(pth)-1]) if err != nil { // 404 if there is no command at that path return nil, ErrNotFound } if sub := cmd.Subcommand(pth[len(pth)-1]); sub == nil { if len(pth) <= 1 { return nil, ErrNotFound } // if the last string in the path isn't a subcommand, use it as an argument // e.g. /objects/Qabc12345 (we are passing "Qabc12345" to the "objects" command) stringArgs = append(stringArgs, pth[len(pth)-1]) pth = pth[:len(pth)-1] } else { cmd = sub } opts, stringArgs2 := parseOptions(r) stringArgs = append(stringArgs, stringArgs2...) // count required argument definitions numRequired := 0 for _, argDef := range cmd.Arguments { if argDef.Required { numRequired++ } } // count the number of provided argument values valCount := len(stringArgs) args := make([]string, valCount) valIndex := 0 requiredFile := "" for _, argDef := range cmd.Arguments { // skip optional argument definitions if there aren't sufficient remaining values if valCount-valIndex <= numRequired && !argDef.Required { continue } else if argDef.Required { numRequired-- } if argDef.Type == cmds.ArgString { if argDef.Variadic { for _, s := range stringArgs { args[valIndex] = s valIndex++ } valCount -= len(stringArgs) } else if len(stringArgs) > 0 { args[valIndex] = stringArgs[0] stringArgs = stringArgs[1:] valIndex++ } else { break } } else if argDef.Type == cmds.ArgFile && argDef.Required && len(requiredFile) == 0 { requiredFile = argDef.Name } } optDefs, err := root.GetOptions(pth) if err != nil { return nil, err } // create cmds.File from multipart/form-data contents contentType := r.Header.Get(contentTypeHeader) mediatype, _, _ := mime.ParseMediaType(contentType) var f files.File if mediatype == "multipart/form-data" { reader, err := r.MultipartReader() if err != nil { return nil, err } f = &files.MultipartFile{ Mediatype: mediatype, Reader: reader, } } // if there is a required filearg, error if no files were provided if len(requiredFile) > 0 && f == nil { return nil, fmt.Errorf("File argument '%s' is required", requiredFile) } req, err := cmds.NewRequest(pth, opts, args, f, cmd, optDefs) if err != nil { return nil, err } err = cmd.CheckArguments(req) if err != nil { return nil, err } return req, nil }