@cryptotaxi247 / kubo / commits / 3ead2443e

namesys: Add recursive resolution

This allows direct access to the earlier protocol-specific Resolve implementations. The guts of each protocol-specific resolver are in the internal resolveOnce method, and we've added a new: ResolveN(ctx, name, depth) method to the public interface. There's also: Resolve(ctx, name) which wraps ResolveN using DefaultDepthLimit. The extra API endpoint is intended to reduce the likelyhood of clients accidentally calling the more dangerous ResolveN with a nonsensically high or infinite depth. On IRC on 2015-05-17, Juan said: 15:34 <jbenet> If 90% of uses is the reduced API with no chance to screw it up, that's a huge win. 15:34 <wking> Why would those 90% not just set depth=0 or depth=1, depending on which they need? 15:34 <jbenet> Because people will start writing `r.Resolve(ctx, name, d)` where d is a variable. 15:35 <wking> And then accidentally set that variable to some huge number? 15:35 <jbenet> Grom experience, i've seen this happen _dozens_ of times. people screw trivial things up. 15:35 <wking> Why won't those same people be using ResolveN? 15:36 <jbenet> Because almost every example they see will tell them to use Resolve(), and they will mostly stay away from ResolveN. The per-prodocol versions also resolve recursively within their protocol. For example: DNSResolver.Resolve(ctx, "ipfs.io", 0) will recursively resolve DNS links until the referenced value is no longer a DNS link. I also renamed the multi-protocol ipfs NameSystem (defined in namesys/namesys.go) to 'mpns' (for Multi-Protocol Name System), because I wasn't clear on whether IPNS applied to the whole system or just to to the DHT-based system. The new name is unambiguously multi-protocol, which is good. It would be nice to have a distinct name for the DHT-based link system. Now that resolver output is always prefixed with a namespace and unprefixed mpns resolver input is interpreted as /ipfs/, core/corehttp/ipns_hostname.go can dispense with it's old manual /ipfs/ injection. Now that the Resolver interface handles recursion, we don't need the resolveRecurse helper in core/pathresolver.go. The pathresolver cleanup also called for an adjustment to FromSegments to more easily get slash-prefixed paths. Now that recursive resolution with the namesys/namesys.go composite resolver always gets you to an /ipfs/... path, there's no need for the /ipns/ special case in fuse/ipns/ipns_unix.go. Now that DNS links can be things other than /ipfs/ or DHT-link references (e.g. they could be /ipns/<domain-name> references) I've also loosened the ParsePath logic to only attempt multihash validation on IPFS paths. It checks to ensure that other paths have a known-protocol prefix, but otherwise leaves them alone. I also changed some key-stringification from .Pretty() to .String() following the potential deprecation mentioned in util/key.go.

W. Trevor King committed May 7, 2015 at 14:31 UTC 3ead2443e5cd55ef551b811ac0a6764ed65c3ec1
15 files changed +229 -122
core/commands/resolve.go
+1 -1
@@ -75,7 +75,7 @@ Resolve te value of another name:
75 name = req.Arguments()[0]
76 }
77
78 - output, err := n.Namesys.Resolve(n.Context(), name)
78 + output, err := n.Namesys.Resolve(n.Context(), "/ipns/"+name)
79 if err != nil {
80 res.SetError(err, cmds.ErrNormal)
81 return
core/corehttp/gateway_test.go
+4 -5
@@ -22,6 +22,10 @@ import (
22 type mockNamesys map[string]path.Path
23
24 func (m mockNamesys) Resolve(ctx context.Context, name string) (value path.Path, err error) {
25 + return m.ResolveN(ctx, name, namesys.DefaultDepthLimit)
26 +}
27 +
28 +func (m mockNamesys) ResolveN(ctx context.Context, name string, depth int) (value path.Path, err error) {
29 p, ok := m[name]
30 if !ok {
31 return "", namesys.ErrResolveFailed
@@ -29,11 +33,6 @@ func (m mockNamesys) Resolve(ctx context.Context, name string) (value path.Path,
33 return p, nil
34 }
35
32 -func (m mockNamesys) CanResolve(name string) bool {
33 - _, ok := m[name]
34 - return ok
35 -}
36 -
36 func (m mockNamesys) Publish(ctx context.Context, name ci.PrivKey, value path.Path) error {
37 return errors.New("not implemented for mockNamesys")
38 }
core/corehttp/ipns_hostname.go
+1 -1
@@ -20,7 +20,7 @@ func IPNSHostnameOption() ServeOption {
20
21 host := strings.SplitN(r.Host, ":", 2)[0]
22 if p, err := n.Namesys.Resolve(ctx, host); err == nil {
23 - r.URL.Path = "/ipfs/" + p.String() + r.URL.Path
23 + r.URL.Path = p.String() + r.URL.Path
24 }
25 childMux.ServeHTTP(w, r)
26 })
core/pathresolver.go
+20 -43
@@ -2,7 +2,6 @@ package core
2
3 import (
4 "errors"
5 - "fmt"
5 "strings"
6
7 context "github.com/ipfs/go-ipfs/Godeps/_workspace/src/golang.org/x/net/context"
@@ -11,64 +10,42 @@ import (
10 path "github.com/ipfs/go-ipfs/path"
11 )
12
14 -const maxLinks = 32
13 +// ErrNoNamesys is an explicit error for when an IPFS node doesn't
14 +// (yet) have a name system
15 +var ErrNoNamesys = errors.New(
16 + "core/resolve: no Namesys on IpfsNode - can't resolve ipns entry")
17
16 -// errors returned by Resolve function
17 -var (
18 - ErrTooManyLinks = errors.New("core/resolve: exceeded maximum number of links in ipns entry")
19 - ErrNoNamesys = errors.New("core/resolve: no Namesys on IpfsNode - can't resolve ipns entry")
20 -)
21 -
22 -// Resolve resolves the given path by parsing out /ipns/ entries and then going
23 -// through the /ipfs/ entries and returning the final merkledage node.
24 -// Effectively enables /ipns/ in CLI commands.
18 +// Resolve resolves the given path by parsing out protocol-specific
19 +// entries (e.g. /ipns/<node-key>) and then going through the /ipfs/
20 +// entries and returning the final merkledage node. Effectively
21 +// enables /ipns/, /dns/, etc. in commands.
22 func Resolve(ctx context.Context, n *IpfsNode, p path.Path) (*merkledag.Node, error) {
26 - r := resolver{ctx, n, p}
27 - return r.resolveRecurse(0)
28 -}
29 -
30 -type resolver struct {
31 - ctx context.Context
32 - n *IpfsNode
33 - p path.Path
34 -}
35 -
36 -func (r *resolver) resolveRecurse(depth int) (*merkledag.Node, error) {
37 - if depth >= maxLinks {
38 - return nil, ErrTooManyLinks
39 - }
40 - // for now, we only try to resolve ipns paths if
41 - // they begin with "/ipns/". Otherwise, ambiguity
42 - // emerges when resolving just a <hash>. Is it meant
43 - // to be an ipfs or an ipns resolution?
44 -
45 - if strings.HasPrefix(r.p.String(), "/ipns/") {
23 + if strings.HasPrefix(p.String(), "/") {
24 + // namespaced path (/ipfs/..., /ipns/..., etc.)
25 // TODO(cryptix): we sould be able to query the local cache for the path
47 - if r.n.Namesys == nil {
26 + if n.Namesys == nil {
27 return nil, ErrNoNamesys
28 }
50 - // if it's an ipns path, try to resolve it.
51 - // if we can't, we can give that error back to the user.
52 - seg := r.p.Segments()
53 - if len(seg) < 2 || seg[1] == "" { // just "/ipns/"
54 - return nil, fmt.Errorf("invalid path: %s", string(r.p))
55 - }
29
57 - ipnsPath := seg[1]
30 + seg := p.Segments()
31 extensions := seg[2:]
59 - respath, err := r.n.Namesys.Resolve(r.ctx, ipnsPath)
32 + resolvable, err := path.FromSegments("/", seg[0], seg[1])
33 + if err != nil {
34 + return nil, err
35 + }
36 +
37 + respath, err := n.Namesys.Resolve(ctx, resolvable.String())
38 if err != nil {
39 return nil, err
40 }
41
42 segments := append(respath.Segments(), extensions...)
65 - r.p, err = path.FromSegments(segments...)
43 + p, err = path.FromSegments("/", segments...)
44 if err != nil {
45 return nil, err
46 }
69 - return r.resolveRecurse(depth + 1)
47 }
48
49 // ok, we have an ipfs path now (or what we'll treat as one)
73 - return r.n.Resolver.ResolvePath(r.ctx, r.p)
50 + return n.Resolver.ResolvePath(ctx, p)
51 }
fuse/ipns/ipns_test.go
+4 -4
@@ -462,7 +462,7 @@ func TestFastRepublish(t *testing.T) {
462 if err != nil {
463 t.Fatal(err)
464 }
465 - pubkeyHash := u.Key(h).Pretty()
465 + pubkeyPath := "/ipns/" + u.Key(h).String()
466
467 // set them back
468 defer func() {
@@ -482,9 +482,9 @@ func TestFastRepublish(t *testing.T) {
482 writeFileData(t, dataA, fname) // random
483 <-time.After(shortRepublishTimeout * 2)
484 log.Debug("resolving first hash")
485 - resolvedHash, err := node.Namesys.Resolve(context.Background(), pubkeyHash)
485 + resolvedHash, err := node.Namesys.Resolve(context.Background(), pubkeyPath)
486 if err != nil {
487 - t.Fatal("resolve err:", pubkeyHash, err)
487 + t.Fatal("resolve err:", pubkeyPath, err)
488 }
489
490 // constantly keep writing to the file
@@ -501,7 +501,7 @@ func TestFastRepublish(t *testing.T) {
501 }(shortRepublishTimeout)
502
503 hasPublished := func() bool {
504 - res, err := node.Namesys.Resolve(context.Background(), pubkeyHash)
504 + res, err := node.Namesys.Resolve(context.Background(), pubkeyPath)
505 if err != nil {
506 t.Fatalf("resolve err: %v", err)
507 }
fuse/ipns/ipns_unix.go
-3
@@ -150,9 +150,6 @@ func (s *Root) Lookup(ctx context.Context, name string) (fs.Node, error) {
150 if segments[0] == "ipfs" {
151 p := strings.Join(resolved.Segments()[1:], "/")
152 return &Link{s.IpfsRoot + "/" + p}, nil
153 - } else if segments[0] == "ipns" {
154 - p := strings.Join(resolved.Segments()[1:], "/")
155 - return &Link{s.IpnsRoot + "/" + p}, nil
153 } else {
154 log.Error("Invalid path.Path: ", resolved)
155 return nil, errors.New("invalid path from ipns record")
ipnsfs/system.go
+1 -1
@@ -141,7 +141,7 @@ func (fs *Filesystem) newKeyRoot(parent context.Context, k ci.PrivKey) (*KeyRoot
141 return nil, err
142 }
143
144 - name := u.Key(hash).Pretty()
144 + name := "/ipns/" + u.Key(hash).String()
145
146 root := new(KeyRoot)
147 root.key = k
namesys/base.go new
+54
@@ -0,0 +1,54 @@
1 +package namesys
2 +
3 +import (
4 + "strings"
5 +
6 + context "github.com/ipfs/go-ipfs/Godeps/_workspace/src/golang.org/x/net/context"
7 +
8 + path "github.com/ipfs/go-ipfs/path"
9 +)
10 +
11 +type resolver interface {
12 + // resolveOnce looks up a name once (without recursion).
13 + resolveOnce(ctx context.Context, name string) (value path.Path, err error)
14 +}
15 +
16 +// resolve is a helper for implementing Resolver.ResolveN using resolveOnce.
17 +func resolve(ctx context.Context, r resolver, name string, depth int, prefixes ...string) (path.Path, error) {
18 + for {
19 + p, err := r.resolveOnce(ctx, name)
20 + if err != nil {
21 + log.Warningf("Could not resolve %s", name)
22 + return "", err
23 + }
24 + log.Debugf("Resolved %s to %s", name, p.String())
25 +
26 + if strings.HasPrefix(p.String(), "/ipfs/") {
27 + // we've bottomed out with an IPFS path
28 + return p, nil
29 + }
30 +
31 + if depth == 1 {
32 + return p, ErrResolveRecursion
33 + }
34 +
35 + matched := false
36 + for _, prefix := range prefixes {
37 + if strings.HasPrefix(p.String(), prefix) {
38 + matched = true
39 + if len(prefixes) == 1 {
40 + name = strings.TrimPrefix(p.String(), prefix)
41 + }
42 + break
43 + }
44 + }
45 +
46 + if !matched {
47 + return p, nil
48 + }
49 +
50 + if depth > 1 {
51 + depth--
52 + }
53 + }
54 +}
namesys/dns.go
+16 -7
@@ -17,16 +17,25 @@ type DNSResolver struct {
17 // cache would need a timeout
18 }
19
20 -// CanResolve implements Resolver
21 -func (r *DNSResolver) CanResolve(name string) bool {
22 - return isd.IsDomain(name)
20 +// Resolve implements Resolver.
21 +func (r *DNSResolver) Resolve(ctx context.Context, name string) (path.Path, error) {
22 + return r.ResolveN(ctx, name, DefaultDepthLimit)
23 +}
24 +
25 +// ResolveN implements Resolver.
26 +func (r *DNSResolver) ResolveN(ctx context.Context, name string, depth int) (path.Path, error) {
27 + return resolve(ctx, r, name, depth, "/ipns/")
28 }
29
25 -// Resolve implements Resolver
30 +// resolveOnce implements resolver.
31 // TXT records for a given domain name should contain a b58
32 // encoded multihash.
28 -func (r *DNSResolver) Resolve(ctx context.Context, name string) (path.Path, error) {
29 - log.Info("DNSResolver resolving %v", name)
33 +func (r *DNSResolver) resolveOnce(ctx context.Context, name string) (path.Path, error) {
34 + if !isd.IsDomain(name) {
35 + return "", errors.New("not a valid domain name")
36 + }
37 +
38 + log.Infof("DNSResolver resolving %s", name)
39 txt, err := net.LookupTXT(name)
40 if err != nil {
41 return "", err
@@ -43,7 +52,7 @@ func (r *DNSResolver) Resolve(ctx context.Context, name string) (path.Path, erro
52 }
53
54 func parseEntry(txt string) (path.Path, error) {
46 - p, err := path.ParseKeyToPath(txt)
55 + p, err := path.ParseKeyToPath(txt) // bare IPFS multihashes
56 if err == nil {
57 return p, nil
58 }
namesys/interface.go
+37 -3
@@ -37,9 +37,24 @@ import (
37 path "github.com/ipfs/go-ipfs/path"
38 )
39
40 +const (
41 + // DefaultDepthLimit is the default depth limit used by Resolve.
42 + DefaultDepthLimit = 32
43 +
44 + // UnlimitedDepth allows infinite recursion in ResolveN. You
45 + // probably don't want to use this, but it's here if you absolutely
46 + // trust resolution to eventually complete and can't put an upper
47 + // limit on how many steps it will take.
48 + UnlimitedDepth = 0
49 +)
50 +
51 // ErrResolveFailed signals an error when attempting to resolve.
52 var ErrResolveFailed = errors.New("could not resolve name.")
53
54 +// ErrResolveRecursion signals a recursion-depth limit.
55 +var ErrResolveRecursion = errors.New(
56 + "could not resolve name (recursion limit exceeded).")
57 +
58 // ErrPublishFailed signals an error when attempting to publish.
59 var ErrPublishFailed = errors.New("could not publish name.")
60
@@ -58,11 +73,30 @@ type NameSystem interface {
73 // Resolver is an object capable of resolving names.
74 type Resolver interface {
75
61 - // Resolve looks up a name, and returns the value previously published.
76 + // Resolve performs a recursive lookup, returning the dereferenced
77 + // path. For example, if ipfs.io has a DNS TXT record pointing to
78 + // /ipns/QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy
79 + // and there is a DHT IPNS entry for
80 + // QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy
81 + // -> /ipfs/Qmcqtw8FfrVSBaRmbWwHxt3AuySBhJLcvmFYi3Lbc4xnwj
82 + // then
83 + // Resolve(ctx, "/ipns/ipfs.io")
84 + // will resolve both names, returning
85 + // /ipfs/Qmcqtw8FfrVSBaRmbWwHxt3AuySBhJLcvmFYi3Lbc4xnwj
86 + //
87 + // There is a default depth-limit to avoid infinite recursion. Most
88 + // users will be fine with this default limit, but if you need to
89 + // adjust the limit you can use ResolveN.
90 Resolve(ctx context.Context, name string) (value path.Path, err error)
91
64 - // CanResolve checks whether this Resolver can resolve a name
65 - CanResolve(name string) bool
92 + // ResolveN performs a recursive lookup, returning the dereferenced
93 + // path. The only difference from Resolve is that the depth limit
94 + // is configurable. You can use DefaultDepthLimit, UnlimitedDepth,
95 + // or a depth limit of your own choosing.
96 + //
97 + // Most users should use Resolve, since the default limit works well
98 + // in most real-world situations.
99 + ResolveN(ctx context.Context, name string, depth int) (value path.Path, err error)
100 }
101
102 // Publisher is an object capable of publishing particular names.
namesys/namesys.go
+50 -26
@@ -1,59 +1,83 @@
1 package namesys
2
3 import (
4 + "strings"
5 +
6 context "github.com/ipfs/go-ipfs/Godeps/_workspace/src/golang.org/x/net/context"
7 ci "github.com/ipfs/go-ipfs/p2p/crypto"
8 path "github.com/ipfs/go-ipfs/path"
9 routing "github.com/ipfs/go-ipfs/routing"
10 )
11
10 -// ipnsNameSystem implements IPNS naming.
12 +// mpns (a multi-protocol NameSystem) implements generic IPFS naming.
13 //
12 -// Uses three Resolvers:
14 +// Uses several Resolvers:
15 // (a) ipfs routing naming: SFS-like PKI names.
16 // (b) dns domains: resolves using links in DNS TXT records
17 // (c) proquints: interprets string as the raw byte data.
18 //
19 // It can only publish to: (a) ipfs routing naming.
20 //
19 -type ipns struct {
20 - resolvers []Resolver
21 - publisher Publisher
21 +type mpns struct {
22 + resolvers map[string]resolver
23 + publishers map[string]Publisher
24 }
25
26 // NewNameSystem will construct the IPFS naming system based on Routing
27 func NewNameSystem(r routing.IpfsRouting) NameSystem {
26 - return &ipns{
27 - resolvers: []Resolver{
28 - new(DNSResolver),
29 - new(ProquintResolver),
30 - NewRoutingResolver(r),
28 + return &mpns{
29 + resolvers: map[string]resolver{
30 + "dns": new(DNSResolver),
31 + "proquint": new(ProquintResolver),
32 + "dht": newRoutingResolver(r),
33 + },
34 + publishers: map[string]Publisher{
35 + "/ipns/": NewRoutingPublisher(r),
36 },
32 - publisher: NewRoutingPublisher(r),
37 }
38 }
39
36 -// Resolve implements Resolver
37 -func (ns *ipns) Resolve(ctx context.Context, name string) (path.Path, error) {
38 - for _, r := range ns.resolvers {
39 - if r.CanResolve(name) {
40 - return r.Resolve(ctx, name)
41 - }
40 +// Resolve implements Resolver.
41 +func (ns *mpns) Resolve(ctx context.Context, name string) (path.Path, error) {
42 + return ns.ResolveN(ctx, name, DefaultDepthLimit)
43 +}
44 +
45 +// ResolveN implements Resolver.
46 +func (ns *mpns) ResolveN(ctx context.Context, name string, depth int) (path.Path, error) {
47 + if strings.HasPrefix(name, "/ipfs/") {
48 + return path.ParsePath(name)
49 }
43 - return "", ErrResolveFailed
50 +
51 + if !strings.HasPrefix(name, "/") {
52 + return path.ParsePath("/ipfs/" + name)
53 + }
54 +
55 + return resolve(ctx, ns, name, depth, "/ipns/")
56 }
57
46 -// CanResolve implements Resolver
47 -func (ns *ipns) CanResolve(name string) bool {
48 - for _, r := range ns.resolvers {
49 - if r.CanResolve(name) {
50 - return true
58 +// resolveOnce implements resolver.
59 +func (ns *mpns) resolveOnce(ctx context.Context, name string) (path.Path, error) {
60 + if !strings.HasPrefix(name, "/ipns/") {
61 + name = "/ipns/" + name
62 + }
63 + segments := strings.SplitN(name, "/", 3)
64 + if len(segments) < 3 || segments[0] != "" {
65 + log.Warningf("Invalid name syntax for %s", name)
66 + return "", ErrResolveFailed
67 + }
68 +
69 + for protocol, resolver := range ns.resolvers {
70 + log.Debugf("Attempting to resolve %s with %s", name, protocol)
71 + p, err := resolver.resolveOnce(ctx, segments[2])
72 + if err == nil {
73 + return p, err
74 }
75 }
53 - return false
76 + log.Warningf("No resolver found for %s", name)
77 + return "", ErrResolveFailed
78 }
79
80 // Publish implements Publisher
57 -func (ns *ipns) Publish(ctx context.Context, name ci.PrivKey, value path.Path) error {
58 - return ns.publisher.Publish(ctx, name, value)
81 +func (ns *mpns) Publish(ctx context.Context, name ci.PrivKey, value path.Path) error {
82 + return ns.publishers["/ipns/"].Publish(ctx, name, value)
83 }
namesys/proquint.go
+12 -8
@@ -10,16 +10,20 @@ import (
10
11 type ProquintResolver struct{}
12
13 -// CanResolve implements Resolver. Checks whether the name is a proquint string.
14 -func (r *ProquintResolver) CanResolve(name string) bool {
15 - ok, err := proquint.IsProquint(name)
16 - return err == nil && ok
13 +// Resolve implements Resolver.
14 +func (r *ProquintResolver) Resolve(ctx context.Context, name string) (path.Path, error) {
15 + return r.ResolveN(ctx, name, DefaultDepthLimit)
16 }
17
19 -// Resolve implements Resolver. Decodes the proquint string.
20 -func (r *ProquintResolver) Resolve(ctx context.Context, name string) (path.Path, error) {
21 - ok := r.CanResolve(name)
22 - if !ok {
18 +// ResolveN implements Resolver.
19 +func (r *ProquintResolver) ResolveN(ctx context.Context, name string, depth int) (path.Path, error) {
20 + return resolve(ctx, r, name, depth, "/ipns/")
21 +}
22 +
23 +// resolveOnce implements resolver. Decodes the proquint string.
24 +func (r *ProquintResolver) resolveOnce(ctx context.Context, name string) (path.Path, error) {
25 + ok, err := proquint.IsProquint(name)
26 + if err != nil || !ok {
27 return "", errors.New("not a valid proquint string")
28 }
29 return path.FromString(string(proquint.Decode(name))), nil
namesys/routing.go
+19 -6
@@ -30,15 +30,28 @@ func NewRoutingResolver(route routing.IpfsRouting) Resolver {
30 return &routingResolver{routing: route}
31 }
32
33 -// CanResolve implements Resolver. Checks whether name is a b58 encoded string.
34 -func (r *routingResolver) CanResolve(name string) bool {
35 - _, err := mh.FromB58String(name)
36 - return err == nil
33 +// newRoutingResolver returns a resolver instead of a Resolver.
34 +func newRoutingResolver(route routing.IpfsRouting) resolver {
35 + if route == nil {
36 + panic("attempt to create resolver with nil routing system")
37 + }
38 +
39 + return &routingResolver{routing: route}
40 }
41
39 -// Resolve implements Resolver. Uses the IPFS routing system to resolve SFS-like
40 -// names.
42 +// Resolve implements Resolver.
43 func (r *routingResolver) Resolve(ctx context.Context, name string) (path.Path, error) {
44 + return r.ResolveN(ctx, name, DefaultDepthLimit)
45 +}
46 +
47 +// ResolveN implements Resolver.
48 +func (r *routingResolver) ResolveN(ctx context.Context, name string, depth int) (path.Path, error) {
49 + return resolve(ctx, r, name, depth, "/ipns/")
50 +}
51 +
52 +// resolveOnce implements resolver. Uses the IPFS routing system to
53 +// resolve SFS-like names.
54 +func (r *routingResolver) resolveOnce(ctx context.Context, name string) (path.Path, error) {
55 log.Debugf("RoutingResolve: '%s'", name)
56 hash, err := mh.FromB58String(name)
57 if err != nil {
path/path.go
+8 -12
@@ -44,12 +44,8 @@ func (p Path) String() string {
44 return string(p)
45 }
46
47 -func FromSegments(seg ...string) (Path, error) {
48 - var pref string
49 - if seg[0] == "ipfs" || seg[0] == "ipns" {
50 - pref = "/"
51 - }
52 - return ParsePath(pref + strings.Join(seg, "/"))
47 +func FromSegments(prefix string, seg ...string) (Path, error) {
48 + return ParsePath(prefix + strings.Join(seg, "/"))
49 }
50
51 func ParsePath(txt string) (Path, error) {
@@ -68,15 +64,15 @@ func ParsePath(txt string) (Path, error) {
64 return "", ErrBadPath
65 }
66
71 - if parts[1] != "ipfs" && parts[1] != "ipns" {
67 + if parts[1] == "ipfs" {
68 + _, err := ParseKeyToPath(parts[2])
69 + if err != nil {
70 + return "", err
71 + }
72 + } else if parts[1] != "ipns" {
73 return "", ErrBadPath
74 }
75
75 - _, err := ParseKeyToPath(parts[2])
76 - if err != nil {
77 - return "", err
78 - }
79 -
76 return Path(txt), nil
77 }
78
path/resolver_test.go
+2 -2
@@ -59,8 +59,8 @@ func TestRecurivePathResolution(t *testing.T) {
59 t.Fatal(err)
60 }
61
62 - segments := []string{"", "ipfs", aKey.String(), "child", "grandchild"}
63 - p, err := path.FromSegments(segments...)
62 + segments := []string{aKey.String(), "child", "grandchild"}
63 + p, err := path.FromSegments("/ipfs/", segments...)
64 if err != nil {
65 t.Fatal(err)
66 }