realmpath.gno
4.27 Kb · 115 lines
1// Package realmpath is a lightweight Render.path parsing and link generation
2// library with an idiomatic API, closely resembling that of net/url.
3//
4// This package provides utilities for parsing request paths and query
5// parameters, allowing you to extract path segments and manipulate query
6// values.
7//
8// Example usage:
9//
10// import "gno.land/p/moul/realmpath"
11//
12// func Render(path string) string {
13// // Parsing a sample path with query parameters
14// path = "hello/world?foo=bar&baz=foobar"
15// req := realmpath.Parse(path)
16//
17// // Accessing parsed path and query parameters
18// println(req.Path) // Output: hello/world
19// println(req.PathPart(0)) // Output: hello
20// println(req.PathPart(1)) // Output: world
21// println(req.Query.Get("foo")) // Output: bar
22// println(req.Query.Get("baz")) // Output: foobar
23//
24// // Rebuilding the URL
25// println(req.String()) // Output: /r/current/realm:hello/world?baz=foobar&foo=bar
26// }
27package realmpath
28
29import (
30 "chain/runtime"
31 "chain/runtime/unsafe"
32 "net/url"
33 "strings"
34)
35
36var chainDomain = runtime.ChainDomain()
37
38// Request represents a parsed request.
39type Request struct {
40 Path string // The path of the request
41 Query url.Values // The parsed query parameters
42 Realm string // The realm associated with the request
43}
44
45// Parse takes a raw path string and returns a Request object.
46// It splits the path into its components and parses any query parameters.
47func Parse(rawPath string) *Request {
48 // Split the raw path into path and query components
49 path, query := splitPathAndQuery(rawPath)
50
51 // Parse the query string into url.Values
52 queryValues, _ := url.ParseQuery(query)
53
54 return &Request{
55 Path: path, // Set the path
56 Query: queryValues, // Set the parsed query values
57 }
58}
59
60// PathParts returns the segments of the path as a slice of strings.
61// It trims leading and trailing slashes and splits the path by slashes.
62func (r *Request) PathParts() []string {
63 return strings.Split(strings.Trim(r.Path, "/"), "/")
64}
65
66// PathPart returns the specified part of the path.
67// If the index is out of bounds, it returns an empty string.
68func (r *Request) PathPart(index int) string {
69 parts := r.PathParts() // Get the path segments
70 if index < 0 || index >= len(parts) {
71 return "" // Return empty if index is out of bounds
72 }
73 return parts[index] // Return the specified path part
74}
75
76// String rebuilds the URL from the path and query values.
77// If the Realm is not set, it automatically retrieves the current realm path.
78//
79// SECURITY (Class-2-shaped, intentionally accepted): unsafe.CurrentRealm()
80// inside a non-crossing /p/ method is .Title()-vulnerable — it walks past
81// non-crossing frames to the most-recent crossing ancestor, not the
82// immediate caller. The lazily-captured r.Realm is therefore NOT a reliable
83// identity claim under a contrived call chain.
84//
85// This is acceptable here because r.Realm is consumed only as a URL string
86// for rendering (the line below builds reconstructedPath for display); no
87// caller in /examples uses it for authorization. If you add a new consumer
88// that gates writes/auth on r.Realm, replace the lazy fill with an explicit
89// `req.Realm = cur.PkgPath()` set by the caller under rlm.IsCurrent(), or
90// switch to a sibling method that takes a realm parameter — see
91// docs/resources/gno-security.md for the threat-class taxonomy.
92func (r *Request) String() string {
93 // Automatically set the Realm if it is not already defined
94 if r.Realm == "" {
95 r.Realm = unsafe.CurrentRealm().PkgPath() // Get the current realm path
96 }
97
98 // Rebuild the path using the realm and path parts
99 relativePkgPath := strings.TrimPrefix(r.Realm, chainDomain) // Trim the chain domain prefix
100 reconstructedPath := relativePkgPath + ":" + strings.Join(r.PathParts(), "/")
101
102 // Rebuild the query string
103 queryString := r.Query.Encode() // Encode the query parameters
104 if queryString != "" {
105 return reconstructedPath + "?" + queryString // Return the full URL with query
106 }
107 return reconstructedPath // Return the path without query parameters
108}
109
110func splitPathAndQuery(rawPath string) (string, string) {
111 if idx := strings.Index(rawPath, "?"); idx != -1 {
112 return rawPath[:idx], rawPath[idx+1:] // Split at the first '?' found
113 }
114 return rawPath, "" // No query string present
115}