| 1 | package commands |
| 2 | |
| 3 | import ( |
| 4 | "errors" |
| 5 | "fmt" |
| 6 | "io" |
| 7 | "strings" |
| 8 | "time" |
| 9 | |
| 10 | ns "github.com/ipfs/boxo/namesys" |
| 11 | cidenc "github.com/ipfs/go-cidutil/cidenc" |
| 12 | cmdenv "github.com/ipfs/kubo/core/commands/cmdenv" |
| 13 | "github.com/ipfs/kubo/core/commands/cmdutils" |
| 14 | ncmd "github.com/ipfs/kubo/core/commands/name" |
| 15 | |
| 16 | "github.com/ipfs/boxo/path" |
| 17 | cmds "github.com/ipfs/go-ipfs-cmds" |
| 18 | options "github.com/ipfs/kubo/core/coreiface/options" |
| 19 | ) |
| 20 | |
| 21 | const ( |
| 22 | resolveRecursiveOptionName = "recursive" |
| 23 | resolveDhtRecordCountOptionName = "dht-record-count" |
| 24 | resolveDhtTimeoutOptionName = "dht-timeout" |
| 25 | ) |
| 26 | |
| 27 | var ResolveCmd = &cmds.Command{ |
| 28 | Helptext: cmds.HelpText{ |
| 29 | Tagline: "Resolve the value of names to IPFS.", |
| 30 | ShortDescription: ` |
| 31 | There are a number of mutable name protocols that can link among |
| 32 | themselves and into IPNS. This command accepts any of these |
| 33 | identifiers and resolves them to the referenced item. |
| 34 | `, |
| 35 | LongDescription: ` |
| 36 | There are a number of mutable name protocols that can link among |
| 37 | themselves and into IPNS. For example IPNS references can (currently) |
| 38 | point at an IPFS object, and DNS links can point at other DNS links, IPNS |
| 39 | entries, or IPFS objects. This command accepts any of these |
| 40 | identifiers and resolves them to the referenced item. |
| 41 | |
| 42 | EXAMPLES |
| 43 | |
| 44 | Resolve the value of your identity: |
| 45 | |
| 46 | $ ipfs resolve /ipns/QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy |
| 47 | /ipfs/Qmcqtw8FfrVSBaRmbWwHxt3AuySBhJLcvmFYi3Lbc4xnwj |
| 48 | |
| 49 | Resolve the value of another name: |
| 50 | |
| 51 | $ ipfs resolve /ipns/QmbCMUZw6JFeZ7Wp9jkzbye3Fzp2GGcPgC3nmeUjfVF87n |
| 52 | /ipns/QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy |
| 53 | |
| 54 | Resolve the value of another name recursively: |
| 55 | |
| 56 | $ ipfs resolve -r /ipns/QmbCMUZw6JFeZ7Wp9jkzbye3Fzp2GGcPgC3nmeUjfVF87n |
| 57 | /ipfs/Qmcqtw8FfrVSBaRmbWwHxt3AuySBhJLcvmFYi3Lbc4xnwj |
| 58 | |
| 59 | Resolve the value of an IPFS DAG path: |
| 60 | |
| 61 | $ ipfs resolve /ipfs/QmeZy1fGbwgVSrqbfh9fKQrAWgeyRnj7h8fsHS1oy3k99x/beep/boop |
| 62 | /ipfs/QmYRMjyvAiHKN9UTi8Bzt1HUspmSRD8T8DwxfSMzLgBon1 |
| 63 | |
| 64 | `, |
| 65 | }, |
| 66 | |
| 67 | Arguments: []cmds.Argument{ |
| 68 | cmds.StringArg("name", true, false, "The name to resolve.").EnableStdin(), |
| 69 | }, |
| 70 | Options: []cmds.Option{ |
| 71 | cmds.BoolOption(resolveRecursiveOptionName, "r", "Resolve until the result is an IPFS name.").WithDefault(true), |
| 72 | cmds.IntOption(resolveDhtRecordCountOptionName, "dhtrc", "Number of records to request for DHT resolution."), |
| 73 | cmds.StringOption(resolveDhtTimeoutOptionName, "dhtt", "Max time to collect values during DHT resolution e.g. \"30s\". Pass 0 for no timeout."), |
| 74 | }, |
| 75 | Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error { |
| 76 | api, err := cmdenv.GetApi(env, req) |
| 77 | if err != nil { |
| 78 | return err |
| 79 | } |
| 80 | |
| 81 | name := req.Arguments[0] |
| 82 | recursive, _ := req.Options[resolveRecursiveOptionName].(bool) |
| 83 | |
| 84 | // the case when ipns is resolved step by step |
| 85 | if strings.HasPrefix(name, "/ipns/") && !recursive { |
| 86 | rc, rcok := req.Options[resolveDhtRecordCountOptionName].(uint) |
| 87 | dhtt, dhttok := req.Options[resolveDhtTimeoutOptionName].(string) |
| 88 | ropts := []options.NameResolveOption{ |
| 89 | options.Name.ResolveOption(ns.ResolveWithDepth(1)), |
| 90 | } |
| 91 | |
| 92 | if rcok { |
| 93 | ropts = append(ropts, options.Name.ResolveOption(ns.ResolveWithDhtRecordCount(rc))) |
| 94 | } |
| 95 | if dhttok { |
| 96 | d, err := time.ParseDuration(dhtt) |
| 97 | if err != nil { |
| 98 | return err |
| 99 | } |
| 100 | if d < 0 { |
| 101 | return errors.New("DHT timeout value must be >= 0") |
| 102 | } |
| 103 | ropts = append(ropts, options.Name.ResolveOption(ns.ResolveWithDhtTimeout(d))) |
| 104 | } |
| 105 | p, err := api.Name().Resolve(req.Context, name, ropts...) |
| 106 | // ErrResolveRecursion is fine |
| 107 | if err != nil && err != ns.ErrResolveRecursion { |
| 108 | return err |
| 109 | } |
| 110 | return cmds.EmitOnce(res, &ncmd.ResolvedPath{Path: p.String()}) |
| 111 | } |
| 112 | |
| 113 | var enc cidenc.Encoder |
| 114 | switch { |
| 115 | case !cmdenv.CidBaseDefined(req) && !strings.HasPrefix(name, "/ipns/"): |
| 116 | // Not specified, check the path. |
| 117 | enc, err = cmdenv.CidEncoderFromPath(name) |
| 118 | if err == nil { |
| 119 | break |
| 120 | } |
| 121 | // Nope, fallback on the default. |
| 122 | fallthrough |
| 123 | default: |
| 124 | enc, err = cmdenv.GetCidEncoder(req) |
| 125 | if err != nil { |
| 126 | return err |
| 127 | } |
| 128 | } |
| 129 | |
| 130 | p, err := cmdutils.PathOrCidPath(name) |
| 131 | if err != nil { |
| 132 | return err |
| 133 | } |
| 134 | |
| 135 | // else, ipfs path or ipns with recursive flag |
| 136 | rp, remainder, err := api.ResolvePath(req.Context, p) |
| 137 | if err != nil { |
| 138 | return err |
| 139 | } |
| 140 | |
| 141 | // Trick to encode path with correct encoding. |
| 142 | encodedPath := "/" + rp.Namespace() + "/" + enc.Encode(rp.RootCid()) |
| 143 | if len(remainder) != 0 { |
| 144 | encodedPath += path.SegmentsToString(remainder...) |
| 145 | } |
| 146 | |
| 147 | // Ensure valid and sanitized. |
| 148 | ep, err := path.NewPath(encodedPath) |
| 149 | if err != nil { |
| 150 | return err |
| 151 | } |
| 152 | |
| 153 | return cmds.EmitOnce(res, &ncmd.ResolvedPath{Path: ep.String()}) |
| 154 | }, |
| 155 | Encoders: cmds.EncoderMap{ |
| 156 | cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, rp *ncmd.ResolvedPath) error { |
| 157 | fmt.Fprintln(w, rp.Path) |
| 158 | return nil |
| 159 | }), |
| 160 | }, |
| 161 | Type: ncmd.ResolvedPath{}, |
| 162 | } |