diff --git a/core/commands/dns.go b/core/commands/dns.go new file mode 100644 index 00000000000..e24b6a483d5 --- /dev/null +++ b/core/commands/dns.go @@ -0,0 +1,82 @@ +package commands + +import ( + "io" + "strings" + + cmds "github.com/ipfs/go-ipfs/commands" + namesys "github.com/ipfs/go-ipfs/namesys" + util "github.com/ipfs/go-ipfs/util" +) + +var DNSCmd = &cmds.Command{ + Helptext: cmds.HelpText{ + Tagline: "DNS link resolver", + ShortDescription: ` +Multihashes are hard to remember, but domain names are usually easy to +remember. To create memorable aliases for multihashes, DNS TXT +records can point to other DNS links, IPFS objects, IPNS keys, etc. +This command resolves those links to the referenced object. +`, + LongDescription: ` +Multihashes are hard to remember, but domain names are usually easy to +remember. To create memorable aliases for multihashes, DNS TXT +records can point to other DNS links, IPFS objects, IPNS keys, etc. +This command resolves those links to the referenced object. + +For example, with this DNS TXT record: + + ipfs.io. TXT "dnslink=/ipfs/QmRzTuh2Lpuz7Gr39stNr6mTFdqAghsZec1JoUnfySUzcy ..." + +The resolver will give: + + > ipfs dns ipfs.io + /ipfs/QmRzTuh2Lpuz7Gr39stNr6mTFdqAghsZec1JoUnfySUzcy + +And with this DNS TXT record: + + ipfs.ipfs.io. TXT "dnslink=/dns/ipfs.io ..." + +The resolver will give: + + > ipfs dns ipfs.io + /dns/ipfs.io + > ipfs dns --recursive + /ipfs/QmRzTuh2Lpuz7Gr39stNr6mTFdqAghsZec1JoUnfySUzcy +`, + }, + + Arguments: []cmds.Argument{ + cmds.StringArg("domain-name", true, false, "The domain-name name to resolve.").EnableStdin(), + }, + Options: []cmds.Option{ + cmds.BoolOption("recursive", "r", "Resolve until the result is not a DNS link"), + }, + Run: func(req cmds.Request, res cmds.Response) { + + recursive, _, _ := req.Option("recursive").Bool() + name := req.Arguments()[0] + resolver := namesys.NewDNSResolver() + + depth := 1 + if recursive { + depth = namesys.DefaultDepthLimit + } + output, err := resolver.ResolveN(req.Context().Context, name, depth) + if err != nil { + res.SetError(err, cmds.ErrNormal) + return + } + res.SetOutput(&ResolvedPath{output}) + }, + Marshalers: cmds.MarshalerMap{ + cmds.Text: func(res cmds.Response) (io.Reader, error) { + output, ok := res.Output().(*ResolvedPath) + if !ok { + return nil, util.ErrCast() + } + return strings.NewReader(output.Path.String()), nil + }, + }, + Type: ResolvedPath{}, +} diff --git a/core/commands/ipns.go b/core/commands/ipns.go new file mode 100644 index 00000000000..76a455ef522 --- /dev/null +++ b/core/commands/ipns.go @@ -0,0 +1,104 @@ +package commands + +import ( + "errors" + "io" + "strings" + + cmds "github.com/ipfs/go-ipfs/commands" + namesys "github.com/ipfs/go-ipfs/namesys" + u "github.com/ipfs/go-ipfs/util" +) + +var ipnsCmd = &cmds.Command{ + Helptext: cmds.HelpText{ + Tagline: "Gets the value currently published at an IPNS name", + ShortDescription: ` +IPNS is a PKI namespace, where names are the hashes of public keys, and +the private key enables publishing new (signed) values. In resolve, the +default value of is your own identity public key. +`, + LongDescription: ` +IPNS is a PKI namespace, where names are the hashes of public keys, and +the private key enables publishing new (signed) values. In resolve, the +default value of is your own identity public key. + + +Examples: + +Resolve the value of your identity: + + > ipfs name resolve + QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy + +Resolve the value of another name: + + > ipfs name resolve QmbCMUZw6JFeZ7Wp9jkzbye3Fzp2GGcPgC3nmeUjfVF87n + QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy + +`, + }, + + Arguments: []cmds.Argument{ + cmds.StringArg("name", false, false, "The IPNS name to resolve. Defaults to your node's peerID.").EnableStdin(), + }, + Options: []cmds.Option{ + cmds.BoolOption("recursive", "r", "Resolve until the result is not an IPNS name"), + }, + Run: func(req cmds.Request, res cmds.Response) { + + n, err := req.Context().GetNode() + if err != nil { + res.SetError(err, cmds.ErrNormal) + return + } + + if !n.OnlineMode() { + err := n.SetupOfflineRouting() + if err != nil { + res.SetError(err, cmds.ErrNormal) + return + } + } + + var name string + + if len(req.Arguments()) == 0 { + if n.Identity == "" { + res.SetError(errors.New("Identity not loaded!"), cmds.ErrNormal) + return + } + name = n.Identity.Pretty() + + } else { + name = req.Arguments()[0] + } + + recursive, _, _ := req.Option("recursive").Bool() + depth := 1 + if recursive { + depth = namesys.DefaultDepthLimit + } + + resolver := namesys.NewRoutingResolver(n.Routing) + output, err := resolver.ResolveN(n.Context(), name, depth) + if err != nil { + res.SetError(err, cmds.ErrNormal) + return + } + + // TODO: better errors (in the case of not finding the name, we get "failed to find any peer in table") + + res.SetOutput(&ResolvedPath{output}) + }, + Marshalers: cmds.MarshalerMap{ + cmds.Text: func(res cmds.Response) (io.Reader, error) { + output, ok := res.Output().(*ResolvedPath) + if !ok { + return nil, u.ErrCast() + } + return strings.NewReader(output.Path.String()), nil + }, + }, + Type: ResolvedPath{}, +} diff --git a/core/commands/name.go b/core/commands/name.go index d272d9e4ed8..eba937304fe 100644 --- a/core/commands/name.go +++ b/core/commands/name.go @@ -27,31 +27,31 @@ and resolve, the default value of is your own identity public key. Examples: -Publish a to your identity name: +Publish an to your identity name: - > ipfs name publish QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy - published name QmbCMUZw6JFeZ7Wp9jkzbye3Fzp2GGcPgC3nmeUjfVF87n to QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy + > ipfs name publish /ipfs/QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy + Published to QmbCMUZw6JFeZ7Wp9jkzbye3Fzp2GGcPgC3nmeUjfVF87n: /ipfs/QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy -Publish a to another public key: +Publish an to another public key: - > ipfs name publish QmbCMUZw6JFeZ7Wp9jkzbye3Fzp2GGcPgC3nmeUjfVF87n QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy - published name QmbCMUZw6JFeZ7Wp9jkzbye3Fzp2GGcPgC3nmeUjfVF87n to QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy + > ipfs name publish /ipfs/QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy QmbCMUZw6JFeZ7Wp9jkzbye3Fzp2GGcPgC3nmeUjfVF87n + Published to QmbCMUZw6JFeZ7Wp9jkzbye3Fzp2GGcPgC3nmeUjfVF87n: /ipfs/QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy Resolve the value of your identity: > ipfs name resolve - QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy + /ipns/QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy Resolve the value of another name: > ipfs name resolve QmbCMUZw6JFeZ7Wp9jkzbye3Fzp2GGcPgC3nmeUjfVF87n - QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy + /ipns/QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy `, }, Subcommands: map[string]*cmds.Command{ "publish": publishCmd, - "resolve": resolveCmd, + "resolve": ipnsCmd, }, } diff --git a/core/commands/publish.go b/core/commands/publish.go index c784bbcd245..2f52bf3560d 100644 --- a/core/commands/publish.go +++ b/core/commands/publish.go @@ -35,12 +35,13 @@ Examples: Publish an to your identity name: > ipfs name publish /ipfs/QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy - published name QmbCMUZw6JFeZ7Wp9jkzbye3Fzp2GGcPgC3nmeUjfVF87n to QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy + Published to QmbCMUZw6JFeZ7Wp9jkzbye3Fzp2GGcPgC3nmeUjfVF87n: /ipfs/QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy Publish an to another public key (not implemented): - > ipfs name publish QmbCMUZw6JFeZ7Wp9jkzbye3Fzp2GGcPgC3nmeUjfVF87n QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy - published name QmbCMUZw6JFeZ7Wp9jkzbye3Fzp2GGcPgC3nmeUjfVF87n to QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy + > ipfs name publish /ipfs/QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy QmbCMUZw6JFeZ7Wp9jkzbye3Fzp2GGcPgC3nmeUjfVF87n + Published to QmbCMUZw6JFeZ7Wp9jkzbye3Fzp2GGcPgC3nmeUjfVF87n: /ipfs/QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy + `, }, @@ -102,7 +103,7 @@ Publish an to another public key (not implemented): Marshalers: cmds.MarshalerMap{ cmds.Text: func(res cmds.Response) (io.Reader, error) { v := res.Output().(*IpnsEntry) - s := fmt.Sprintf("Published name %s to %s\n", v.Name, v.Value) + s := fmt.Sprintf("Published to %s: %s\n", v.Name, v.Value) return strings.NewReader(s), nil }, }, diff --git a/core/commands/resolve.go b/core/commands/resolve.go index e84d9dabe66..9e8845cafa8 100644 --- a/core/commands/resolve.go +++ b/core/commands/resolve.go @@ -1,11 +1,11 @@ package commands import ( - "errors" "io" "strings" cmds "github.com/ipfs/go-ipfs/commands" + namesys "github.com/ipfs/go-ipfs/namesys" path "github.com/ipfs/go-ipfs/path" u "github.com/ipfs/go-ipfs/util" ) @@ -14,37 +14,46 @@ type ResolvedPath struct { Path path.Path } -var resolveCmd = &cmds.Command{ +var ResolveCmd = &cmds.Command{ Helptext: cmds.HelpText{ - Tagline: "Gets the value currently published at an IPNS name", + Tagline: "Resolve the value of names to IPFS", ShortDescription: ` -IPNS is a PKI namespace, where names are the hashes of public keys, and -the private key enables publishing new (signed) values. In resolve, the -default value of is your own identity public key. +There are a number of mutable name protocols that can link among +themselves and into IPNS. This command accepts any of these +identifiers and resolves them to the referenced item. `, LongDescription: ` -IPNS is a PKI namespace, where names are the hashes of public keys, and -the private key enables publishing new (signed) values. In resolve, the -default value of is your own identity public key. - +There are a number of mutable name protocols that can link among +themselves and into IPNS. For example IPNS references can (currently) +point at IPFS object, and DNS links can point at other DNS links, IPNS +entries, or IPFS objects. This command accepts any of these +identifiers and resolves them to the referenced item. Examples: Resolve the value of your identity: - > ipfs name resolve - QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy + > ipfs resolve /ipns/QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy + /ipfs/Qmcqtw8FfrVSBaRmbWwHxt3AuySBhJLcvmFYi3Lbc4xnwj + +Resolve the value of another name: + + > ipfs resolve /ipns/QmbCMUZw6JFeZ7Wp9jkzbye3Fzp2GGcPgC3nmeUjfVF87n + /ipns/QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy -Resolve te value of another name: +Resolve the value of another name recursively: - > ipfs name resolve QmbCMUZw6JFeZ7Wp9jkzbye3Fzp2GGcPgC3nmeUjfVF87n - QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy + > ipfs resolve -r /ipns/QmbCMUZw6JFeZ7Wp9jkzbye3Fzp2GGcPgC3nmeUjfVF87n + /ipfs/Qmcqtw8FfrVSBaRmbWwHxt3AuySBhJLcvmFYi3Lbc4xnwj `, }, Arguments: []cmds.Argument{ - cmds.StringArg("name", false, false, "The IPNS name to resolve. Defaults to your node's peerID.").EnableStdin(), + cmds.StringArg("name", true, false, "The name to resolve.").EnableStdin(), + }, + Options: []cmds.Option{ + cmds.BoolOption("recursive", "r", "Resolve until the result is an IPFS name"), }, Run: func(req cmds.Request, res cmds.Response) { @@ -62,27 +71,19 @@ Resolve te value of another name: } } - var name string - - if len(req.Arguments()) == 0 { - if n.Identity == "" { - res.SetError(errors.New("Identity not loaded!"), cmds.ErrNormal) - return - } - name = n.Identity.Pretty() - - } else { - name = req.Arguments()[0] + name := req.Arguments()[0] + recursive, _, _ := req.Option("recursive").Bool() + depth := 1 + if recursive { + depth = namesys.DefaultDepthLimit } - output, err := n.Namesys.Resolve(n.Context(), name) + output, err := n.Namesys.ResolveN(n.Context(), name, depth) if err != nil { res.SetError(err, cmds.ErrNormal) return } - // TODO: better errors (in the case of not finding the name, we get "failed to find any peer in table") - res.SetOutput(&ResolvedPath{output}) }, Marshalers: cmds.MarshalerMap{ diff --git a/core/commands/root.go b/core/commands/root.go index 5b1ef364d48..5de32a21325 100644 --- a/core/commands/root.go +++ b/core/commands/root.go @@ -40,7 +40,9 @@ ADVANCED COMMANDS daemon Start a long-running daemon process mount Mount an ipfs read-only mountpoint + resolve Resolve any type of name name Publish or resolve IPNS names + dns Resolve DNS links pin Pin objects to local storage repo gc Garbage collect unpinned objects @@ -84,6 +86,7 @@ var rootSubcommands = map[string]*cmds.Command{ "config": ConfigCmd, "dht": DhtCmd, "diag": DiagCmd, + "dns": DNSCmd, "get": GetCmd, "id": IDCmd, "log": LogCmd, @@ -95,6 +98,7 @@ var rootSubcommands = map[string]*cmds.Command{ "ping": PingCmd, "refs": RefsCmd, "repo": RepoCmd, + "resolve": ResolveCmd, "stats": StatsCmd, "swarm": SwarmCmd, "update": UpdateCmd, diff --git a/core/corehttp/gateway_test.go b/core/corehttp/gateway_test.go index 818338c1c8a..01d4295b719 100644 --- a/core/corehttp/gateway_test.go +++ b/core/corehttp/gateway_test.go @@ -22,6 +22,10 @@ import ( type mockNamesys map[string]path.Path func (m mockNamesys) Resolve(ctx context.Context, name string) (value path.Path, err error) { + return m.ResolveN(ctx, name, namesys.DefaultDepthLimit) +} + +func (m mockNamesys) ResolveN(ctx context.Context, name string, depth int) (value path.Path, err error) { p, ok := m[name] if !ok { return "", namesys.ErrResolveFailed @@ -29,11 +33,6 @@ func (m mockNamesys) Resolve(ctx context.Context, name string) (value path.Path, return p, nil } -func (m mockNamesys) CanResolve(name string) bool { - _, ok := m[name] - return ok -} - func (m mockNamesys) Publish(ctx context.Context, name ci.PrivKey, value path.Path) error { return errors.New("not implemented for mockNamesys") } diff --git a/core/corehttp/ipns_hostname.go b/core/corehttp/ipns_hostname.go index 7361001d1e4..a6e8e91f5bb 100644 --- a/core/corehttp/ipns_hostname.go +++ b/core/corehttp/ipns_hostname.go @@ -20,7 +20,7 @@ func IPNSHostnameOption() ServeOption { host := strings.SplitN(r.Host, ":", 2)[0] if p, err := n.Namesys.Resolve(ctx, host); err == nil { - r.URL.Path = "/ipfs/" + p.String() + r.URL.Path + r.URL.Path = p.String() + r.URL.Path } childMux.ServeHTTP(w, r) }) diff --git a/core/pathresolver.go b/core/pathresolver.go index d439e2082c8..f73a83fd364 100644 --- a/core/pathresolver.go +++ b/core/pathresolver.go @@ -2,7 +2,6 @@ package core import ( "errors" - "fmt" "strings" context "github.com/ipfs/go-ipfs/Godeps/_workspace/src/golang.org/x/net/context" @@ -11,64 +10,42 @@ import ( path "github.com/ipfs/go-ipfs/path" ) -const maxLinks = 32 +// ErrNoNamesys is an explicit error for when an IPFS node doesn't +// (yet) have a name system +var ErrNoNamesys = errors.New( + "core/resolve: no Namesys on IpfsNode - can't resolve ipns entry") -// errors returned by Resolve function -var ( - ErrTooManyLinks = errors.New("core/resolve: exceeded maximum number of links in ipns entry") - ErrNoNamesys = errors.New("core/resolve: no Namesys on IpfsNode - can't resolve ipns entry") -) - -// Resolve resolves the given path by parsing out /ipns/ entries and then going -// through the /ipfs/ entries and returning the final merkledage node. -// Effectively enables /ipns/ in CLI commands. +// Resolve resolves the given path by parsing out protocol-specific +// entries (e.g. /ipns/) and then going through the /ipfs/ +// entries and returning the final merkledage node. Effectively +// enables /ipns/, /dns/, etc. in commands. func Resolve(ctx context.Context, n *IpfsNode, p path.Path) (*merkledag.Node, error) { - r := resolver{ctx, n, p} - return r.resolveRecurse(0) -} - -type resolver struct { - ctx context.Context - n *IpfsNode - p path.Path -} - -func (r *resolver) resolveRecurse(depth int) (*merkledag.Node, error) { - if depth >= maxLinks { - return nil, ErrTooManyLinks - } - // for now, we only try to resolve ipns paths if - // they begin with "/ipns/". Otherwise, ambiguity - // emerges when resolving just a . Is it meant - // to be an ipfs or an ipns resolution? - - if strings.HasPrefix(r.p.String(), "/ipns/") { + if strings.HasPrefix(p.String(), "/") { + // namespaced path (/ipfs/..., /ipns/..., etc.) // TODO(cryptix): we sould be able to query the local cache for the path - if r.n.Namesys == nil { + if n.Namesys == nil { return nil, ErrNoNamesys } - // if it's an ipns path, try to resolve it. - // if we can't, we can give that error back to the user. - seg := r.p.Segments() - if len(seg) < 2 || seg[1] == "" { // just "/ipns/" - return nil, fmt.Errorf("invalid path: %s", string(r.p)) - } - ipnsPath := seg[1] + seg := p.Segments() extensions := seg[2:] - respath, err := r.n.Namesys.Resolve(r.ctx, ipnsPath) + resolvable, err := path.FromSegments("/", seg[0], seg[1]) + if err != nil { + return nil, err + } + + respath, err := n.Namesys.Resolve(ctx, resolvable.String()) if err != nil { return nil, err } segments := append(respath.Segments(), extensions...) - r.p, err = path.FromSegments(segments...) + p, err = path.FromSegments("/", segments...) if err != nil { return nil, err } - return r.resolveRecurse(depth + 1) } // ok, we have an ipfs path now (or what we'll treat as one) - return r.n.Resolver.ResolvePath(r.ctx, r.p) + return n.Resolver.ResolvePath(ctx, p) } diff --git a/fuse/ipns/ipns_test.go b/fuse/ipns/ipns_test.go index d3dda0658fe..8ac2c403a27 100644 --- a/fuse/ipns/ipns_test.go +++ b/fuse/ipns/ipns_test.go @@ -462,7 +462,7 @@ func TestFastRepublish(t *testing.T) { if err != nil { t.Fatal(err) } - pubkeyHash := u.Key(h).Pretty() + pubkeyPath := "/ipns/" + u.Key(h).String() // set them back defer func() { @@ -482,9 +482,9 @@ func TestFastRepublish(t *testing.T) { writeFileData(t, dataA, fname) // random <-time.After(shortRepublishTimeout * 2) log.Debug("resolving first hash") - resolvedHash, err := node.Namesys.Resolve(context.Background(), pubkeyHash) + resolvedHash, err := node.Namesys.Resolve(context.Background(), pubkeyPath) if err != nil { - t.Fatal("resolve err:", pubkeyHash, err) + t.Fatal("resolve err:", pubkeyPath, err) } // constantly keep writing to the file @@ -501,7 +501,7 @@ func TestFastRepublish(t *testing.T) { }(shortRepublishTimeout) hasPublished := func() bool { - res, err := node.Namesys.Resolve(context.Background(), pubkeyHash) + res, err := node.Namesys.Resolve(context.Background(), pubkeyPath) if err != nil { t.Fatalf("resolve err: %v", err) } diff --git a/fuse/ipns/ipns_unix.go b/fuse/ipns/ipns_unix.go index 8f2a772a2f4..6cf39c9f843 100644 --- a/fuse/ipns/ipns_unix.go +++ b/fuse/ipns/ipns_unix.go @@ -150,9 +150,6 @@ func (s *Root) Lookup(ctx context.Context, name string) (fs.Node, error) { if segments[0] == "ipfs" { p := strings.Join(resolved.Segments()[1:], "/") return &Link{s.IpfsRoot + "/" + p}, nil - } else if segments[0] == "ipns" { - p := strings.Join(resolved.Segments()[1:], "/") - return &Link{s.IpnsRoot + "/" + p}, nil } else { log.Error("Invalid path.Path: ", resolved) return nil, errors.New("invalid path from ipns record") diff --git a/ipnsfs/system.go b/ipnsfs/system.go index ecdb4c4a135..46b33425599 100644 --- a/ipnsfs/system.go +++ b/ipnsfs/system.go @@ -141,7 +141,7 @@ func (fs *Filesystem) newKeyRoot(parent context.Context, k ci.PrivKey) (*KeyRoot return nil, err } - name := u.Key(hash).Pretty() + name := "/ipns/" + u.Key(hash).String() root := new(KeyRoot) root.key = k diff --git a/namesys/base.go b/namesys/base.go new file mode 100644 index 00000000000..e552fce464a --- /dev/null +++ b/namesys/base.go @@ -0,0 +1,54 @@ +package namesys + +import ( + "strings" + + context "github.com/ipfs/go-ipfs/Godeps/_workspace/src/golang.org/x/net/context" + + path "github.com/ipfs/go-ipfs/path" +) + +type resolver interface { + // resolveOnce looks up a name once (without recursion). + resolveOnce(ctx context.Context, name string) (value path.Path, err error) +} + +// resolve is a helper for implementing Resolver.ResolveN using resolveOnce. +func resolve(ctx context.Context, r resolver, name string, depth int, prefixes ...string) (path.Path, error) { + for { + p, err := r.resolveOnce(ctx, name) + if err != nil { + log.Warningf("Could not resolve %s", name) + return "", err + } + log.Debugf("Resolved %s to %s", name, p.String()) + + if strings.HasPrefix(p.String(), "/ipfs/") { + // we've bottomed out with an IPFS path + return p, nil + } + + if depth == 1 { + return p, ErrResolveRecursion + } + + matched := false + for _, prefix := range prefixes { + if strings.HasPrefix(p.String(), prefix) { + matched = true + if len(prefixes) == 1 { + name = strings.TrimPrefix(p.String(), prefix) + } + break + } + } + + if !matched { + return p, nil + } + + if depth > 1 { + depth-- + } + } +} diff --git a/namesys/dns.go b/namesys/dns.go index 086adee9eef..3703bd8d01d 100644 --- a/namesys/dns.go +++ b/namesys/dns.go @@ -11,23 +11,46 @@ import ( path "github.com/ipfs/go-ipfs/path" ) +type LookupTXTFunc func(name string) (txt []string, err error) + // DNSResolver implements a Resolver on DNS domains type DNSResolver struct { + lookupTXT LookupTXTFunc // TODO: maybe some sort of caching? // cache would need a timeout } -// CanResolve implements Resolver -func (r *DNSResolver) CanResolve(name string) bool { - return isd.IsDomain(name) +// NewDNSResolver constructs a name resolver using DNS TXT records. +func NewDNSResolver() Resolver { + return &DNSResolver{lookupTXT: net.LookupTXT} +} + +// newDNSResolver constructs a name resolver using DNS TXT records, +// returning a resolver instead of NewDNSResolver's Resolver. +func newDNSResolver() resolver { + return &DNSResolver{lookupTXT: net.LookupTXT} +} + +// Resolve implements Resolver. +func (r *DNSResolver) Resolve(ctx context.Context, name string) (path.Path, error) { + return r.ResolveN(ctx, name, DefaultDepthLimit) } -// Resolve implements Resolver +// ResolveN implements Resolver. +func (r *DNSResolver) ResolveN(ctx context.Context, name string, depth int) (path.Path, error) { + return resolve(ctx, r, name, depth, "/ipns/") +} + +// resolveOnce implements resolver. // TXT records for a given domain name should contain a b58 // encoded multihash. -func (r *DNSResolver) Resolve(ctx context.Context, name string) (path.Path, error) { - log.Info("DNSResolver resolving %v", name) - txt, err := net.LookupTXT(name) +func (r *DNSResolver) resolveOnce(ctx context.Context, name string) (path.Path, error) { + if !isd.IsDomain(name) { + return "", errors.New("not a valid domain name") + } + + log.Infof("DNSResolver resolving %s", name) + txt, err := r.lookupTXT(name) if err != nil { return "", err } @@ -43,7 +66,7 @@ func (r *DNSResolver) Resolve(ctx context.Context, name string) (path.Path, erro } func parseEntry(txt string) (path.Path, error) { - p, err := path.ParseKeyToPath(txt) + p, err := path.ParseKeyToPath(txt) // bare IPFS multihashes if err == nil { return p, nil } @@ -52,10 +75,10 @@ func parseEntry(txt string) (path.Path, error) { } func tryParseDnsLink(txt string) (path.Path, error) { - parts := strings.Split(txt, "=") - if len(parts) == 1 || parts[0] != "dnslink" { - return "", errors.New("not a valid dnslink entry") + parts := strings.SplitN(txt, "=", 2) + if len(parts) == 2 && parts[0] == "dnslink" { + return path.ParsePath(parts[1]) } - return path.ParsePath(parts[1]) + return "", errors.New("not a valid dnslink entry") } diff --git a/namesys/dns_test.go b/namesys/dns_test.go index 402156addc3..40bf702c35c 100644 --- a/namesys/dns_test.go +++ b/namesys/dns_test.go @@ -1,9 +1,22 @@ package namesys import ( + "fmt" "testing" ) +type mockDNS struct { + entries map[string][]string +} + +func (m *mockDNS) lookupTXT(name string) (txt []string, err error) { + txt, ok := m.entries[name] + if !ok { + return nil, fmt.Errorf("No TXT entry for %s", name) + } + return txt, nil +} + func TestDnsEntryParsing(t *testing.T) { goodEntries := []string{ "QmY3hE8xgFCjGcz6PHgnvJz5HZi1BaKRfPkn1ghZUcYMjD", @@ -40,3 +53,60 @@ func TestDnsEntryParsing(t *testing.T) { } } } + +func newMockDNS() *mockDNS { + return &mockDNS{ + entries: map[string][]string{ + "multihash.example.com": []string{ + "dnslink=QmY3hE8xgFCjGcz6PHgnvJz5HZi1BaKRfPkn1ghZUcYMjD", + }, + "ipfs.example.com": []string{ + "dnslink=/ipfs/QmY3hE8xgFCjGcz6PHgnvJz5HZi1BaKRfPkn1ghZUcYMjD", + }, + "dns1.example.com": []string{ + "dnslink=/ipns/ipfs.example.com", + }, + "dns2.example.com": []string{ + "dnslink=/ipns/dns1.example.com", + }, + "multi.example.com": []string{ + "some stuff", + "dnslink=/ipns/dns1.example.com", + "masked dnslink=/ipns/example.invalid", + }, + "equals.example.com": []string{ + "dnslink=/ipfs/QmY3hE8xgFCjGcz6PHgnvJz5HZi1BaKRfPkn1ghZUcYMjD/=equals", + }, + "loop1.example.com": []string{ + "dnslink=/ipns/loop2.example.com", + }, + "loop2.example.com": []string{ + "dnslink=/ipns/loop1.example.com", + }, + "bad.example.com": []string{ + "dnslink=", + }, + }, + } +} + +func TestDNSResolution(t *testing.T) { + mock := newMockDNS() + r := &DNSResolver{lookupTXT: mock.lookupTXT} + testResolution(t, r, "multihash.example.com", DefaultDepthLimit, "/ipfs/QmY3hE8xgFCjGcz6PHgnvJz5HZi1BaKRfPkn1ghZUcYMjD", nil) + testResolution(t, r, "ipfs.example.com", DefaultDepthLimit, "/ipfs/QmY3hE8xgFCjGcz6PHgnvJz5HZi1BaKRfPkn1ghZUcYMjD", nil) + testResolution(t, r, "dns1.example.com", DefaultDepthLimit, "/ipfs/QmY3hE8xgFCjGcz6PHgnvJz5HZi1BaKRfPkn1ghZUcYMjD", nil) + testResolution(t, r, "dns1.example.com", 1, "/ipns/ipfs.example.com", ErrResolveRecursion) + testResolution(t, r, "dns2.example.com", DefaultDepthLimit, "/ipfs/QmY3hE8xgFCjGcz6PHgnvJz5HZi1BaKRfPkn1ghZUcYMjD", nil) + testResolution(t, r, "dns2.example.com", 1, "/ipns/dns1.example.com", ErrResolveRecursion) + testResolution(t, r, "dns2.example.com", 2, "/ipns/ipfs.example.com", ErrResolveRecursion) + testResolution(t, r, "multi.example.com", DefaultDepthLimit, "/ipfs/QmY3hE8xgFCjGcz6PHgnvJz5HZi1BaKRfPkn1ghZUcYMjD", nil) + testResolution(t, r, "multi.example.com", 1, "/ipns/dns1.example.com", ErrResolveRecursion) + testResolution(t, r, "multi.example.com", 2, "/ipns/ipfs.example.com", ErrResolveRecursion) + testResolution(t, r, "equals.example.com", DefaultDepthLimit, "/ipfs/QmY3hE8xgFCjGcz6PHgnvJz5HZi1BaKRfPkn1ghZUcYMjD/=equals", nil) + testResolution(t, r, "loop1.example.com", 1, "/ipns/loop2.example.com", ErrResolveRecursion) + testResolution(t, r, "loop1.example.com", 2, "/ipns/loop1.example.com", ErrResolveRecursion) + testResolution(t, r, "loop1.example.com", 3, "/ipns/loop2.example.com", ErrResolveRecursion) + testResolution(t, r, "loop1.example.com", DefaultDepthLimit, "/ipns/loop1.example.com", ErrResolveRecursion) + testResolution(t, r, "bad.example.com", DefaultDepthLimit, "", ErrResolveFailed) +} diff --git a/namesys/interface.go b/namesys/interface.go index 4ceb3b9d966..5903c78a394 100644 --- a/namesys/interface.go +++ b/namesys/interface.go @@ -1,4 +1,32 @@ -// package namesys implements various functionality for the ipns naming system. +/* +Package namesys implements resolvers and publishers for the IPFS +naming system (IPNS). + +The core of IPFS is an immutable, content-addressable Merkle graph. +That works well for many use cases, but doesn't allow you to answer +questions like "what is Alice's current homepage?". The mutable name +system allows Alice to publish information like: + + The current homepage for alice.example.com is + /ipfs/Qmcqtw8FfrVSBaRmbWwHxt3AuySBhJLcvmFYi3Lbc4xnwj + +or: + + The current homepage for node + QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy + is + /ipfs/Qmcqtw8FfrVSBaRmbWwHxt3AuySBhJLcvmFYi3Lbc4xnwj + +The mutable name system also allows users to resolve those references +to find the immutable IPFS object currently referenced by a given +mutable name. + +For command-line bindings to this functionality, see: + + ipfs name + ipfs dns + ipfs resolve +*/ package namesys import ( @@ -9,9 +37,24 @@ import ( path "github.com/ipfs/go-ipfs/path" ) +const ( + // DefaultDepthLimit is the default depth limit used by Resolve. + DefaultDepthLimit = 32 + + // UnlimitedDepth allows infinite recursion in ResolveN. You + // probably don't want to use this, but it's here if you absolutely + // trust resolution to eventually complete and can't put an upper + // limit on how many steps it will take. + UnlimitedDepth = 0 +) + // ErrResolveFailed signals an error when attempting to resolve. var ErrResolveFailed = errors.New("could not resolve name.") +// ErrResolveRecursion signals a recursion-depth limit. +var ErrResolveRecursion = errors.New( + "could not resolve name (recursion limit exceeded).") + // ErrPublishFailed signals an error when attempting to publish. var ErrPublishFailed = errors.New("could not publish name.") @@ -30,11 +73,30 @@ type NameSystem interface { // Resolver is an object capable of resolving names. type Resolver interface { - // Resolve looks up a name, and returns the value previously published. + // Resolve performs a recursive lookup, returning the dereferenced + // path. For example, if ipfs.io has a DNS TXT record pointing to + // /ipns/QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy + // and there is a DHT IPNS entry for + // QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy + // -> /ipfs/Qmcqtw8FfrVSBaRmbWwHxt3AuySBhJLcvmFYi3Lbc4xnwj + // then + // Resolve(ctx, "/ipns/ipfs.io") + // will resolve both names, returning + // /ipfs/Qmcqtw8FfrVSBaRmbWwHxt3AuySBhJLcvmFYi3Lbc4xnwj + // + // There is a default depth-limit to avoid infinite recursion. Most + // users will be fine with this default limit, but if you need to + // adjust the limit you can use ResolveN. Resolve(ctx context.Context, name string) (value path.Path, err error) - // CanResolve checks whether this Resolver can resolve a name - CanResolve(name string) bool + // ResolveN performs a recursive lookup, returning the dereferenced + // path. The only difference from Resolve is that the depth limit + // is configurable. You can use DefaultDepthLimit, UnlimitedDepth, + // or a depth limit of your own choosing. + // + // Most users should use Resolve, since the default limit works well + // in most real-world situations. + ResolveN(ctx context.Context, name string, depth int) (value path.Path, err error) } // Publisher is an object capable of publishing particular names. diff --git a/namesys/namesys.go b/namesys/namesys.go index 65530772396..7fe317b6695 100644 --- a/namesys/namesys.go +++ b/namesys/namesys.go @@ -1,59 +1,83 @@ package namesys import ( + "strings" + context "github.com/ipfs/go-ipfs/Godeps/_workspace/src/golang.org/x/net/context" ci "github.com/ipfs/go-ipfs/p2p/crypto" path "github.com/ipfs/go-ipfs/path" routing "github.com/ipfs/go-ipfs/routing" ) -// ipnsNameSystem implements IPNS naming. +// mpns (a multi-protocol NameSystem) implements generic IPFS naming. // -// Uses three Resolvers: +// Uses several Resolvers: // (a) ipfs routing naming: SFS-like PKI names. // (b) dns domains: resolves using links in DNS TXT records // (c) proquints: interprets string as the raw byte data. // // It can only publish to: (a) ipfs routing naming. // -type ipns struct { - resolvers []Resolver - publisher Publisher +type mpns struct { + resolvers map[string]resolver + publishers map[string]Publisher } // NewNameSystem will construct the IPFS naming system based on Routing func NewNameSystem(r routing.IpfsRouting) NameSystem { - return &ipns{ - resolvers: []Resolver{ - new(DNSResolver), - new(ProquintResolver), - NewRoutingResolver(r), + return &mpns{ + resolvers: map[string]resolver{ + "dns": newDNSResolver(), + "proquint": new(ProquintResolver), + "dht": newRoutingResolver(r), + }, + publishers: map[string]Publisher{ + "/ipns/": NewRoutingPublisher(r), }, - publisher: NewRoutingPublisher(r), } } -// Resolve implements Resolver -func (ns *ipns) Resolve(ctx context.Context, name string) (path.Path, error) { - for _, r := range ns.resolvers { - if r.CanResolve(name) { - return r.Resolve(ctx, name) - } +// Resolve implements Resolver. +func (ns *mpns) Resolve(ctx context.Context, name string) (path.Path, error) { + return ns.ResolveN(ctx, name, DefaultDepthLimit) +} + +// ResolveN implements Resolver. +func (ns *mpns) ResolveN(ctx context.Context, name string, depth int) (path.Path, error) { + if strings.HasPrefix(name, "/ipfs/") { + return path.ParsePath(name) } - return "", ErrResolveFailed + + if !strings.HasPrefix(name, "/") { + return path.ParsePath("/ipfs/" + name) + } + + return resolve(ctx, ns, name, depth, "/ipns/") } -// CanResolve implements Resolver -func (ns *ipns) CanResolve(name string) bool { - for _, r := range ns.resolvers { - if r.CanResolve(name) { - return true +// resolveOnce implements resolver. +func (ns *mpns) resolveOnce(ctx context.Context, name string) (path.Path, error) { + if !strings.HasPrefix(name, "/ipns/") { + name = "/ipns/" + name + } + segments := strings.SplitN(name, "/", 3) + if len(segments) < 3 || segments[0] != "" { + log.Warningf("Invalid name syntax for %s", name) + return "", ErrResolveFailed + } + + for protocol, resolver := range ns.resolvers { + log.Debugf("Attempting to resolve %s with %s", name, protocol) + p, err := resolver.resolveOnce(ctx, segments[2]) + if err == nil { + return p, err } } - return false + log.Warningf("No resolver found for %s", name) + return "", ErrResolveFailed } // Publish implements Publisher -func (ns *ipns) Publish(ctx context.Context, name ci.PrivKey, value path.Path) error { - return ns.publisher.Publish(ctx, name, value) +func (ns *mpns) Publish(ctx context.Context, name ci.PrivKey, value path.Path) error { + return ns.publishers["/ipns/"].Publish(ctx, name, value) } diff --git a/namesys/namesys_test.go b/namesys/namesys_test.go new file mode 100644 index 00000000000..256228c3e65 --- /dev/null +++ b/namesys/namesys_test.go @@ -0,0 +1,71 @@ +package namesys + +import ( + "fmt" + "testing" + + context "github.com/ipfs/go-ipfs/Godeps/_workspace/src/golang.org/x/net/context" + + path "github.com/ipfs/go-ipfs/path" +) + +type mockResolver struct { + entries map[string]string +} + +func testResolution(t *testing.T, resolver Resolver, name string, depth int, expected string, expError error) { + p, err := resolver.ResolveN(context.Background(), name, depth) + if err != expError { + t.Fatal(fmt.Errorf( + "Expected %s with a depth of %d to have a '%s' error, but got '%s'", + name, depth, expError, err)) + } + if p.String() != expected { + t.Fatal(fmt.Errorf( + "%s with depth %d resolved to %s != %s", + name, depth, p.String(), expected)) + } +} + +func (r *mockResolver) resolveOnce(ctx context.Context, name string) (path.Path, error) { + return path.ParsePath(r.entries[name]) +} + +func mockResolverOne() *mockResolver { + return &mockResolver{ + entries: map[string]string{ + "QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy": "/ipfs/Qmcqtw8FfrVSBaRmbWwHxt3AuySBhJLcvmFYi3Lbc4xnwj", + "QmbCMUZw6JFeZ7Wp9jkzbye3Fzp2GGcPgC3nmeUjfVF87n": "/ipns/QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy", + "QmY3hE8xgFCjGcz6PHgnvJz5HZi1BaKRfPkn1ghZUcYMjD": "/ipns/ipfs.io", + }, + } +} + +func mockResolverTwo() *mockResolver { + return &mockResolver{ + entries: map[string]string{ + "ipfs.io": "/ipns/QmbCMUZw6JFeZ7Wp9jkzbye3Fzp2GGcPgC3nmeUjfVF87n", + }, + } +} + +func TestNamesysResolution(t *testing.T) { + r := &mpns{ + resolvers: map[string]resolver{ + "one": mockResolverOne(), + "two": mockResolverTwo(), + }, + } + + testResolution(t, r, "Qmcqtw8FfrVSBaRmbWwHxt3AuySBhJLcvmFYi3Lbc4xnwj", DefaultDepthLimit, "/ipfs/Qmcqtw8FfrVSBaRmbWwHxt3AuySBhJLcvmFYi3Lbc4xnwj", nil) + testResolution(t, r, "/ipns/QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy", DefaultDepthLimit, "/ipfs/Qmcqtw8FfrVSBaRmbWwHxt3AuySBhJLcvmFYi3Lbc4xnwj", nil) + testResolution(t, r, "/ipns/QmbCMUZw6JFeZ7Wp9jkzbye3Fzp2GGcPgC3nmeUjfVF87n", DefaultDepthLimit, "/ipfs/Qmcqtw8FfrVSBaRmbWwHxt3AuySBhJLcvmFYi3Lbc4xnwj", nil) + testResolution(t, r, "/ipns/QmbCMUZw6JFeZ7Wp9jkzbye3Fzp2GGcPgC3nmeUjfVF87n", 1, "/ipns/QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy", ErrResolveRecursion) + testResolution(t, r, "/ipns/ipfs.io", DefaultDepthLimit, "/ipfs/Qmcqtw8FfrVSBaRmbWwHxt3AuySBhJLcvmFYi3Lbc4xnwj", nil) + testResolution(t, r, "/ipns/ipfs.io", 1, "/ipns/QmbCMUZw6JFeZ7Wp9jkzbye3Fzp2GGcPgC3nmeUjfVF87n", ErrResolveRecursion) + testResolution(t, r, "/ipns/ipfs.io", 2, "/ipns/QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy", ErrResolveRecursion) + testResolution(t, r, "/ipns/QmY3hE8xgFCjGcz6PHgnvJz5HZi1BaKRfPkn1ghZUcYMjD", DefaultDepthLimit, "/ipfs/Qmcqtw8FfrVSBaRmbWwHxt3AuySBhJLcvmFYi3Lbc4xnwj", nil) + testResolution(t, r, "/ipns/QmY3hE8xgFCjGcz6PHgnvJz5HZi1BaKRfPkn1ghZUcYMjD", 1, "/ipns/ipfs.io", ErrResolveRecursion) + testResolution(t, r, "/ipns/QmY3hE8xgFCjGcz6PHgnvJz5HZi1BaKRfPkn1ghZUcYMjD", 2, "/ipns/QmbCMUZw6JFeZ7Wp9jkzbye3Fzp2GGcPgC3nmeUjfVF87n", ErrResolveRecursion) + testResolution(t, r, "/ipns/QmY3hE8xgFCjGcz6PHgnvJz5HZi1BaKRfPkn1ghZUcYMjD", 3, "/ipns/QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy", ErrResolveRecursion) +} diff --git a/namesys/proquint.go b/namesys/proquint.go index 66bd54e2428..2ad3275a4ad 100644 --- a/namesys/proquint.go +++ b/namesys/proquint.go @@ -10,16 +10,20 @@ import ( type ProquintResolver struct{} -// CanResolve implements Resolver. Checks whether the name is a proquint string. -func (r *ProquintResolver) CanResolve(name string) bool { - ok, err := proquint.IsProquint(name) - return err == nil && ok +// Resolve implements Resolver. +func (r *ProquintResolver) Resolve(ctx context.Context, name string) (path.Path, error) { + return r.ResolveN(ctx, name, DefaultDepthLimit) } -// Resolve implements Resolver. Decodes the proquint string. -func (r *ProquintResolver) Resolve(ctx context.Context, name string) (path.Path, error) { - ok := r.CanResolve(name) - if !ok { +// ResolveN implements Resolver. +func (r *ProquintResolver) ResolveN(ctx context.Context, name string, depth int) (path.Path, error) { + return resolve(ctx, r, name, depth, "/ipns/") +} + +// resolveOnce implements resolver. Decodes the proquint string. +func (r *ProquintResolver) resolveOnce(ctx context.Context, name string) (path.Path, error) { + ok, err := proquint.IsProquint(name) + if err != nil || !ok { return "", errors.New("not a valid proquint string") } return path.FromString(string(proquint.Decode(name))), nil diff --git a/namesys/publisher.go b/namesys/publisher.go index 38dd8d08273..b20a47bea57 100644 --- a/namesys/publisher.go +++ b/namesys/publisher.go @@ -42,7 +42,7 @@ func NewRoutingPublisher(route routing.IpfsRouting) Publisher { // Publish implements Publisher. Accepts a keypair and a value, // and publishes it out to the routing system func (p *ipnsPublisher) Publish(ctx context.Context, k ci.PrivKey, value path.Path) error { - log.Debugf("namesys: Publish %s", value) + log.Debugf("Publish %s", value) data, err := createRoutingEntryData(k, value) if err != nil { diff --git a/namesys/routing.go b/namesys/routing.go index 38cb250d0c2..290c06cb2f1 100644 --- a/namesys/routing.go +++ b/namesys/routing.go @@ -30,15 +30,28 @@ func NewRoutingResolver(route routing.IpfsRouting) Resolver { return &routingResolver{routing: route} } -// CanResolve implements Resolver. Checks whether name is a b58 encoded string. -func (r *routingResolver) CanResolve(name string) bool { - _, err := mh.FromB58String(name) - return err == nil +// newRoutingResolver returns a resolver instead of a Resolver. +func newRoutingResolver(route routing.IpfsRouting) resolver { + if route == nil { + panic("attempt to create resolver with nil routing system") + } + + return &routingResolver{routing: route} } -// Resolve implements Resolver. Uses the IPFS routing system to resolve SFS-like -// names. +// Resolve implements Resolver. func (r *routingResolver) Resolve(ctx context.Context, name string) (path.Path, error) { + return r.ResolveN(ctx, name, DefaultDepthLimit) +} + +// ResolveN implements Resolver. +func (r *routingResolver) ResolveN(ctx context.Context, name string, depth int) (path.Path, error) { + return resolve(ctx, r, name, depth, "/ipns/") +} + +// resolveOnce implements resolver. Uses the IPFS routing system to +// resolve SFS-like names. +func (r *routingResolver) resolveOnce(ctx context.Context, name string) (path.Path, error) { log.Debugf("RoutingResolve: '%s'", name) hash, err := mh.FromB58String(name) if err != nil { diff --git a/path/path.go b/path/path.go index 0ab15e5c96f..ba75810c86d 100644 --- a/path/path.go +++ b/path/path.go @@ -44,12 +44,8 @@ func (p Path) String() string { return string(p) } -func FromSegments(seg ...string) (Path, error) { - var pref string - if seg[0] == "ipfs" || seg[0] == "ipns" { - pref = "/" - } - return ParsePath(pref + strings.Join(seg, "/")) +func FromSegments(prefix string, seg ...string) (Path, error) { + return ParsePath(prefix + strings.Join(seg, "/")) } func ParsePath(txt string) (Path, error) { @@ -68,15 +64,15 @@ func ParsePath(txt string) (Path, error) { return "", ErrBadPath } - if parts[1] != "ipfs" && parts[1] != "ipns" { + if parts[1] == "ipfs" { + _, err := ParseKeyToPath(parts[2]) + if err != nil { + return "", err + } + } else if parts[1] != "ipns" { return "", ErrBadPath } - _, err := ParseKeyToPath(parts[2]) - if err != nil { - return "", err - } - return Path(txt), nil } diff --git a/path/resolver_test.go b/path/resolver_test.go index 3772f1b9b8e..88fcb743370 100644 --- a/path/resolver_test.go +++ b/path/resolver_test.go @@ -59,8 +59,8 @@ func TestRecurivePathResolution(t *testing.T) { t.Fatal(err) } - segments := []string{"", "ipfs", aKey.String(), "child", "grandchild"} - p, err := path.FromSegments(segments...) + segments := []string{aKey.String(), "child", "grandchild"} + p, err := path.FromSegments("/ipfs/", segments...) if err != nil { t.Fatal(err) } diff --git a/test/sharness/t0100-name.sh b/test/sharness/t0100-name.sh index 495323393cb..531eddf9dd9 100755 --- a/test/sharness/t0100-name.sh +++ b/test/sharness/t0100-name.sh @@ -14,11 +14,11 @@ test_init_ipfs test_expect_success "'ipfs name publish' succeeds" ' PEERID=`ipfs id --format=""` && - ipfs name publish "$HASH_WELCOME_DOCS" >publish_out + ipfs name publish "/ipfs/$HASH_WELCOME_DOCS" >publish_out ' test_expect_success "publish output looks good" ' - echo "Published name $PEERID to /ipfs/$HASH_WELCOME_DOCS" >expected1 && + echo "Published to ${PEERID}: /ipfs/$HASH_WELCOME_DOCS" >expected1 && test_cmp publish_out expected1 ' @@ -39,7 +39,7 @@ test_expect_success "'ipfs name publish' succeeds" ' ' test_expect_success "publish a path looks good" ' - echo "Published name $PEERID to /ipfs/$HASH_WELCOME_DOCS/help" >expected3 && + echo "Published to ${PEERID}: /ipfs/$HASH_WELCOME_DOCS/help" >expected3 && test_cmp publish_out expected3 '