mirror of
https://github.com/ipfs/kubo.git
synced 2026-02-24 11:57:44 +08:00
Merge pull request #1208 from wking/dns-resolver
Rework mutable namespace resolution to handle recursion
This commit is contained in:
commit
01e1e71221
82
core/commands/dns.go
Normal file
82
core/commands/dns.go
Normal file
@ -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{},
|
||||
}
|
||||
104
core/commands/ipns.go
Normal file
104
core/commands/ipns.go
Normal file
@ -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 <name> 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 <name> 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{},
|
||||
}
|
||||
@ -27,31 +27,31 @@ and resolve, the default value of <name> is your own identity public key.
|
||||
|
||||
Examples:
|
||||
|
||||
Publish a <ref> to your identity name:
|
||||
Publish an <ipfs-path> 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 <ref> to another public key:
|
||||
Publish an <ipfs-path> 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,
|
||||
},
|
||||
}
|
||||
|
||||
@ -35,12 +35,13 @@ Examples:
|
||||
Publish an <ipfs-path> to your identity name:
|
||||
|
||||
> ipfs name publish /ipfs/QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy
|
||||
published name QmbCMUZw6JFeZ7Wp9jkzbye3Fzp2GGcPgC3nmeUjfVF87n to QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy
|
||||
Published to QmbCMUZw6JFeZ7Wp9jkzbye3Fzp2GGcPgC3nmeUjfVF87n: /ipfs/QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy
|
||||
|
||||
Publish an <ipfs-path> 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 <ipfs-path> 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
|
||||
},
|
||||
},
|
||||
|
||||
@ -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 <name> 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 <name> 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 te value of another name:
|
||||
Resolve the value of another name:
|
||||
|
||||
> ipfs name resolve QmbCMUZw6JFeZ7Wp9jkzbye3Fzp2GGcPgC3nmeUjfVF87n
|
||||
QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy
|
||||
> ipfs resolve /ipns/QmbCMUZw6JFeZ7Wp9jkzbye3Fzp2GGcPgC3nmeUjfVF87n
|
||||
/ipns/QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy
|
||||
|
||||
Resolve the value of another name recursively:
|
||||
|
||||
> 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{
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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")
|
||||
}
|
||||
|
||||
@ -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)
|
||||
})
|
||||
|
||||
@ -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/<node-key>) 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 <hash>. 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))
|
||||
|
||||
seg := p.Segments()
|
||||
extensions := seg[2:]
|
||||
resolvable, err := path.FromSegments("/", seg[0], seg[1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ipnsPath := seg[1]
|
||||
extensions := seg[2:]
|
||||
respath, err := r.n.Namesys.Resolve(r.ctx, ipnsPath)
|
||||
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)
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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")
|
||||
|
||||
@ -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
|
||||
|
||||
54
namesys/base.go
Normal file
54
namesys/base.go
Normal file
@ -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--
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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}
|
||||
}
|
||||
|
||||
// Resolve implements Resolver
|
||||
// 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)
|
||||
}
|
||||
|
||||
// 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")
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(name, "/") {
|
||||
return path.ParsePath("/ipfs/" + name)
|
||||
}
|
||||
|
||||
return resolve(ctx, ns, name, depth, "/ipns/")
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
log.Warningf("No resolver found for %s", name)
|
||||
return "", ErrResolveFailed
|
||||
}
|
||||
|
||||
// CanResolve implements Resolver
|
||||
func (ns *ipns) CanResolve(name string) bool {
|
||||
for _, r := range ns.resolvers {
|
||||
if r.CanResolve(name) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
71
namesys/namesys_test.go
Normal file
71
namesys/namesys_test.go
Normal file
@ -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)
|
||||
}
|
||||
@ -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
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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 {
|
||||
|
||||
20
path/path.go
20
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
|
||||
}
|
||||
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -14,11 +14,11 @@ test_init_ipfs
|
||||
|
||||
test_expect_success "'ipfs name publish' succeeds" '
|
||||
PEERID=`ipfs id --format="<id>"` &&
|
||||
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
|
||||
'
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user