Search Apps Documentation Source Content File Folder Download Copy Actions Download 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}