Skip to content Search Apps Documentation Source Content File Folder Download Copy Actions State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

render.gno

9.42 Kb · 233 lines
  1package registry
  2
  3import (
  4	"chain/runtime"
  5	"chain/runtime/unsafe"
  6	"strconv"
  7	"strings"
  8
  9	pm "gno.land/p/moul/gnopm/v0"
 10	"gno.land/p/moul/md/v0"
 11	"gno.land/p/moul/realmpath/v0"
 12	"gno.land/p/moul/txlink/v0"
 13)
 14
 15// untrusted-render: every STORED field is charset-validated at write time by
 16// gno.land/p/moul/gnopm/v0 (ValidPkgPath, ValidRepo, ValidCommit, ValidDir,
 17// ValidRef). Each is an allowlist, so a stored field cannot hold a backtick, a
 18// pipe, a bracket, a control character or a bidi override, and therefore needs
 19// no escaping at any of the several places one claim is shown. Validating once
 20// at the write beats escaping at every read, and it is why ValidRef is narrower
 21// than git's own rule: one denylist field would have undone it for all of them.
 22//
 23// The one string here that is NOT validated is the render path itself, which
 24// arrives from the URL and is echoed back by notFound. That one is escaped.
 25
 26// pageSize bounds every listing: a Render that walks unbounded state is a
 27// Render that eventually stops rendering.
 28const pageSize = 20
 29
 30// Render routes gnoweb paths:
 31//
 32//	/                    every claimed package path
 33//	/<package path>      the claims about one package
 34//	/help                what this realm is, and how to write to it
 35func Render(path string) string {
 36	req := realmpath.Parse(path)
 37	parts := req.PathParts()
 38
 39	if len(parts) == 0 || parts[0] == "" {
 40		return renderHome(pageOf(req))
 41	}
 42	if len(parts) == 1 && parts[0] == "help" {
 43		return renderHelp()
 44	}
 45	// A package path contains slashes, so it arrives split. Rejoining is the
 46	// whole of the routing: anything that reassembles into a valid path is a
 47	// package page, anything else is a miss.
 48	pkgPath := strings.Join(parts, "/")
 49	if !pm.ValidPkgPath(pkgPath) {
 50		return notFound(pkgPath + " is not a package path")
 51	}
 52	return renderPackage(pkgPath)
 53}
 54
 55func base() string {
 56	return strings.TrimPrefix(unsafe.CurrentRealm().PkgPath(), runtime.ChainDomain())
 57}
 58
 59func pkgURL(pkgPath string) string { return base() + ":" + pkgPath }
 60
 61// link does not escape its text, because every call site passes a field that is
 62// charset-validated at write time (see the note above). Escaping here would
 63// turn "gno.land/p/moul/md/v1" into "gno\.land/p/moul/md/v1" on every row of
 64// every listing, for no safety anyone gains.
 65func link(text, url string) string { return "[" + text + "](" + url + ")" }
 66
 67// join glues blocks with exactly one blank line between them.
 68//
 69// gno collapses two consecutive blank lines inside an example's // Output:
 70// block, exactly as Go does, so a Render that ever emits them cannot be pinned
 71// by an example at all. Appending "\n" by hand as sections are built is how a
 72// Render acquires them; assembling a list of blocks and joining once is how it
 73// stops. Every block arrives without its own leading or trailing blank.
 74func join(blocks []string) string {
 75	for i, b := range blocks {
 76		blocks[i] = strings.Trim(b, "\n")
 77	}
 78	return strings.Join(blocks, "\n\n") + "\n"
 79}
 80
 81func pageOf(req *realmpath.Request) int {
 82	n, err := strconv.Atoi(req.Query.Get("page"))
 83	if err != nil || n < 1 {
 84		return 1
 85	}
 86	return n
 87}
 88
 89// notFound echoes the requested path, which is the only untrusted string this
 90// file handles: it comes from the URL and has already failed validation by the
 91// time we are here. It is escaped rather than dropped, because "that is not a
 92// package path" without saying which one is an unhelpful error.
 93func notFound(why string) string {
 94	return md.H1("Not found") + "\n" + md.EscapeText(why) + ".\n\n" + link("Back to the registry", base()) + "\n"
 95}
 96
 97func renderHome(page int) string {
 98	var b strings.Builder
 99	b.WriteString(md.H1("gnopm source registry"))
100	b.WriteString("\nWhere a deployed package says it came from. Every entry is a claim made by whoever signed it, never a verified fact: this realm cannot clone a repository, so it records testimony in the shape a verifier needs and labels who said it.\n")
101	b.WriteString("\n**Packages:** " + strconv.Itoa(reg.Size()) + " · **Claims:** " + strconv.Itoa(reg.Claims()) + "\n")
102
103	if reg.Size() == 0 {
104		b.WriteString("\nNothing claimed yet. " + link("Register the first package", txlink.Call("Register")) + "\n")
105		b.WriteString("\n" + link("How this works", base()+":help") + "\n")
106		return b.String()
107	}
108
109	b.WriteString("\n| package | claims | owner has claimed |\n")
110	b.WriteString("| --- | ---: | --- |\n")
111	rows := 0
112	reg.IteratePackages((page-1)*pageSize, pageSize, func(p *pm.Package) bool {
113		rows++
114		owner := "no"
115		if _, _, _, ok := OwnerClaim(p.PkgPath); ok {
116			owner = "yes"
117		}
118		b.WriteString("| " + link(p.PkgPath, pkgURL(p.PkgPath)) +
119			" | " + strconv.Itoa(p.Count()) +
120			" | " + owner + " |\n")
121		return false
122	})
123
124	if p := pager(page, rows); p != "" {
125		b.WriteString("\n" + p + "\n")
126	}
127	b.WriteString("\n" + link("How this works", base()+":help") + "\n")
128	return b.String()
129}
130
131func pager(page, rows int) string {
132	var parts []string
133	if page > 1 {
134		parts = append(parts, link("previous", base()+"?page="+strconv.Itoa(page-1)))
135	}
136	if rows == pageSize {
137		parts = append(parts, link("next", base()+"?page="+strconv.Itoa(page+1)))
138	}
139	return strings.Join(parts, " · ")
140}
141
142func renderPackage(pkgPath string) string {
143	p := reg.Package(pkgPath)
144	if p == nil {
145		return join([]string{
146			md.H1(pkgPath),
147			"Nobody has claimed a source for this package.",
148			link("Claim it", txlink.Call("Register", "pkgPath", pkgPath)),
149			link("Back to the registry", base()),
150		})
151	}
152
153	// The namespace owner first, then everyone else. The ordering IS the
154	// tagging: no ranking is stored, it is recomputed here from the one thing
155	// about a claim that the chain can actually prove.
156	var owner, others []*pm.Claim
157	p.IterateClaims(func(c *pm.Claim) bool {
158		if c.OwnedBy(resolveName) {
159			owner = append(owner, c)
160		} else {
161			others = append(others, c)
162		}
163		return false
164	})
165
166	ns := md.InlineCode(pm.Namespace(pkgPath))
167	blocks := []string{
168		md.H1(pkgPath),
169		strconv.Itoa(p.Count()) + " claim(s). A claim is what an address said, not what anyone checked.",
170	}
171	if len(owner) > 0 {
172		blocks = append(blocks,
173			md.H2("Claimed by the namespace owner"),
174			"The signer owns "+ns+", so this claim comes from the party that controls the path. That is not a proof the source matches. It is a proof of who is speaking.")
175		for _, c := range owner {
176			blocks = append(blocks, claimBlock(c))
177		}
178	}
179	if len(others) > 0 {
180		blocks = append(blocks,
181			md.H2("Claimed by others"),
182			"These addresses do not own "+ns+". A third party may be filling in the map honestly, or pointing at a repository that has nothing to do with this package. Read the source before trusting either.")
183		for _, c := range others {
184			blocks = append(blocks, claimBlock(c))
185		}
186	}
187	return join(append(blocks, link("Back to the registry", base())))
188}
189
190func claimBlock(c *pm.Claim) string {
191	var b strings.Builder
192	b.WriteString("| field | value |\n| --- | --- |\n")
193	b.WriteString("| claimant | " + md.InlineCode(c.Claimant.String()) + " |\n")
194	b.WriteString("| commit | " + link(c.Commit[:12], c.SourceURL()) + " |\n")
195	b.WriteString("| repository | " + link(c.Repo, c.Repo) + " |\n")
196	dir := c.Dir
197	if dir == "" {
198		dir = "(repository root)"
199	}
200	b.WriteString("| directory | " + md.InlineCode(dir) + " |\n")
201	if c.Ref != "" {
202		b.WriteString("| ref | " + md.InlineCode(c.Ref) + " |\n")
203	}
204	b.WriteString("| claimed at | block " + strconv.FormatInt(c.Height, 10) + " |\n")
205	updated := "never"
206	if c.UpdatedAt != c.Height {
207		updated = "block " + strconv.FormatInt(c.UpdatedAt, 10)
208	}
209	b.WriteString("| last updated | " + updated + " |")
210	return b.String()
211}
212
213func renderHelp() string {
214	var b strings.Builder
215	b.WriteString(md.H1("How this registry works"))
216	b.WriteString("\nA gno import path is a chain address, not a repository URL, so nothing on chain says where a deployed package came from. This realm is where an address can say so.\n")
217
218	b.WriteString("\n" + md.H2("What it proves"))
219	b.WriteString("\nNothing on its own, and that is the honest answer. A realm cannot clone a repository, so it cannot check that the code at a commit is the code at a path. Three claims get confused with each other and only two of them can ever be settled:\n\n")
220	b.WriteString("| claim | provable |\n| --- | --- |\n")
221	b.WriteString("| this package came from that repository | no. Not here and not anywhere: anyone may deploy any bytes and claim any repository |\n")
222	b.WriteString("| the deployed bytes equal the addpkg payload of that directory at that commit | yes, by hashing both sides. Off chain, by anything that can clone |\n")
223	b.WriteString("| the claimant owns this path's namespace | yes, from chain data, and it is recomputed on every read |\n")
224
225	b.WriteString("\n" + md.H2("Writing to it"))
226	b.WriteString("\nRegister the source of a package you deployed. Calling it again replaces your own claim and nobody else's, which is how you move a claim to a new commit after a redeploy.\n\n")
227	b.WriteString(link("Register", txlink.Call("Register")) + " · " + link("Withdraw", txlink.Call("Withdraw")) + "\n")
228	b.WriteString("\nFields: the full package path, an https repository URL, a 40 or 64 character lowercase hex commit, the subdirectory holding the package (empty for the repository root), and optionally a fully-qualified ref such as " + md.InlineCode("refs/tags/v1.2.0") + ".\n")
229	b.WriteString("\nAnyone may claim any path. A claim from an address that does not own the namespace is not hidden: it is shown under its own heading and ranked below the owner's.\n")
230
231	b.WriteString("\n" + link("Back to the registry", base()) + "\n")
232	return b.String()
233}