pkgpath.gno
3.31 Kb · 110 lines
1package gnopm
2
3import "strings"
4
5// Parsing a gno package path far enough to know who owns it.
6//
7// A path is "<domain>/<kind>/<namespace>/<name...>", optionally ending in a
8// "/vN" version element: "gno.land/p/moul/md/v1", "gno.land/r/sys/users",
9// "gno.land/r/g1abc.../scratch/v0". The namespace is the third element and it
10// is the whole reason this file exists: it decides whether a claim comes from
11// the party that controls the path or from a stranger, which is the strongest
12// signal the registry can offer about a claim it can never itself verify.
13
14// SplitPkgPath breaks a package path into its domain, kind ("p" or "r"),
15// namespace and the remainder. ok is false for anything that is not shaped like
16// a publishable gno path.
17func SplitPkgPath(s string) (domain, kind, ns, rest string, ok bool) {
18 if !ValidPkgPath(s) {
19 return "", "", "", "", false
20 }
21 parts := strings.Split(s, "/")
22 return parts[0], parts[1], parts[2], strings.Join(parts[3:], "/"), true
23}
24
25// Namespace returns the owning namespace of a package path, or "" if the path
26// is not one.
27func Namespace(s string) string {
28 _, _, ns, _, ok := SplitPkgPath(s)
29 if !ok {
30 return ""
31 }
32 return ns
33}
34
35// Version returns the trailing "vN" element of a package path, or "" when the
36// path carries none. Absent is legal and is not an error: gnoweb serves
37// gno.land/u/<name> by calling the realm at exactly /r/<name>/home, so that one
38// path can never carry a version.
39func Version(s string) string {
40 parts := strings.Split(s, "/")
41 if len(parts) < 4 {
42 return ""
43 }
44 last := parts[len(parts)-1]
45 if len(last) < 2 || last[0] != 'v' {
46 return ""
47 }
48 for i := 1; i < len(last); i++ {
49 if last[i] < '0' || last[i] > '9' {
50 return ""
51 }
52 }
53 return last
54}
55
56// ValidPkgPath reports whether s is shaped like a publishable gno package path.
57//
58// It does NOT say the path exists on any chain, and cannot: a realm has no way
59// to ask whether some other path was ever deployed. That gap is the registry's
60// central honesty problem and is documented on Registry.
61func ValidPkgPath(s string) bool {
62 if s == "" || len(s) > MaxPkgPathLen {
63 return false
64 }
65 if strings.HasPrefix(s, "/") || strings.HasSuffix(s, "/") || strings.Contains(s, "//") {
66 return false
67 }
68 parts := strings.Split(s, "/")
69 if len(parts) < 4 {
70 return false
71 }
72 // The domain, which is a host and so must carry a dot.
73 if !strings.Contains(parts[0], ".") {
74 return false
75 }
76 if parts[1] != "p" && parts[1] != "r" {
77 return false
78 }
79 for _, p := range parts {
80 if p == "" || p == "." || p == ".." {
81 return false
82 }
83 for i := 0; i < len(p); i++ {
84 if !pathByteOK(p[i]) {
85 return false
86 }
87 }
88 }
89 return true
90}
91
92// AddressNamespace reports whether ns is shaped like a gno bech32 address, the
93// namespace every account owns without registering anything. A caller still
94// has to check it is the claimant's own address; this only says which of the
95// two ownership rules applies.
96func AddressNamespace(ns string) bool {
97 if len(ns) != 40 || !strings.HasPrefix(ns, "g1") {
98 return false
99 }
100 for i := 2; i < len(ns); i++ {
101 if !strings.ContainsRune(bech32Charset, rune(ns[i])) {
102 return false
103 }
104 }
105 return true
106}
107
108// bech32Charset is bech32's data alphabet: lowercase alphanumerics minus the
109// four characters it drops to avoid transcription errors (1, b, i, o).
110const bech32Charset = "023456789acdefghjklmnpqrstuvwxyz"