tinyrelayfile
tinyrelay/tinygit/metadata.go/

tinygit/metadata.go raw
1package tinygit
2
3import (
4 "errors"
5 "fmt"
6 "strings"
7
8 "github.com/FelineStateMachine/tinyrelay/protocol/nostr"
9)
10
11// Metadata contains the claims parsed from a repository announcement (kind
12// 30617) or state (kind 30618). Author is the event's claimed signer, not a
13// resolved repository owner. Announcement fields are not inherited by states.
14// A Metadata value conveys neither signature validity nor hosting authority.
15type Metadata struct {
16 Kind int
17 Author string
18 Identifier string
19 EventID string
20 Private bool
21 Clone []string
22 Relays []string
23 Maintainers []string
24 Refs map[string]string
25 Head string
26}
27
28// ParseMetadata checks and parses tinygit's supported repository metadata
29// shape: identifier, state ref names and nonzero SHA-1 tips, and symbolic HEAD
30// syntax. HEAD may name a branch or tag not present in Refs, including an unborn
31// branch. Unknown tags and supported extension fields are ignored.
32//
33// This is a pure shape/semantic check, not full NIP or GRASP compliance. It does
34// not validate the event ID or signature, consult announcements or maintainers,
35// authorize hosting, check clone or relay endpoints, or inspect Git objects.
36// Use nostr.Validate separately before trusting Author. GitRelay.Validate and
37// ValidateImported combine signature validation, this parser and host admission;
38// missing Git objects remain a separate pending-state concern.
39func ParseMetadata(e nostr.Event) (Metadata, error) {
40 if e.Kind != 30617 && e.Kind != 30618 {
41 return Metadata{}, fmt.Errorf("unsupported: event kind %d is not GRASP repository metadata", e.Kind)
42 }
43 id := nostr.Tag(e, "d")
44 if !validIdentifier(id) {
45 return Metadata{}, errors.New("invalid: repository identifier")
46 }
47 m := Metadata{Kind: e.Kind, Author: e.PubKey, Identifier: id, EventID: e.ID, Refs: map[string]string{}}
48 if e.Kind == 30617 {
49 for _, tag := range e.Tags {
50 if len(tag) < 2 {
51 continue
52 }
53 switch tag[0] {
54 case "private":
55 m.Private = tag[1] == "true"
56 case "clone":
57 m.Clone = append(m.Clone, tag[1:]...)
58 case "relays":
59 m.Relays = append(m.Relays, tag[1:]...)
60 case "maintainers":
61 m.Maintainers = append(m.Maintainers, tag[1:]...)
62 }
63 }
64 return m, nil
65 }
66 for _, tag := range e.Tags {
67 if len(tag) == 0 {
68 continue
69 }
70 if tag[0] == "HEAD" {
71 if len(tag) != 2 || m.Head != "" {
72 return Metadata{}, errors.New("invalid: repository state has multiple HEAD tags")
73 }
74 m.Head = tag[1]
75 continue
76 }
77 if len(tag) < 2 {
78 continue
79 }
80 if strings.HasPrefix(tag[0], "refs/") {
81 if _, duplicate := m.Refs[tag[0]]; duplicate || tag[1] == "" || !isObjectID(tag[1]) || strings.Trim(tag[1], "0") == "" {
82 return Metadata{}, fmt.Errorf("invalid: ref %s is not a SHA-1", tag[0])
83 }
84 m.Refs[tag[0]] = tag[1]
85 }
86 }
87 if err := validateRefs(m.Refs); err != nil {
88 return Metadata{}, err
89 }
90 if err := validateHead(m.Head, m.Refs); err != nil {
91 return Metadata{}, err
92 }
93 return m, nil
94}
95
96func validateHead(head string, refs map[string]string) error {
97 if head == "" {
98 return nil
99 }
100 if !strings.HasPrefix(head, "ref: ") {
101 return errors.New("invalid: HEAD must use ref: syntax")
102 }
103 ref := strings.TrimPrefix(head, "ref: ")
104 if !strings.HasPrefix(ref, "refs/heads/") && !strings.HasPrefix(ref, "refs/tags/") {
105 return errors.New("invalid: HEAD must name a branch or tag")
106 }
107 if !validRef(ref) {
108 return errors.New("invalid: HEAD ref")
109 }
110 return nil
111}
112
113func validateRefs(refs map[string]string) error {
114 for ref, oid := range refs {
115 if !validRef(ref) || oid != "" && !isObjectID(oid) {
116 return fmt.Errorf("invalid: ref %s", ref)
117 }
118 }
119 return nil
120}

About

ownerc01503e8035f
visibilitypublic
headd730679639bf

Maintainers

  • c01503e8035f owner
  • ba1b5beed5b5 agent hermes

Clone

  • https://012.run/npub1cq2s86qrtadf36eqq3epm38wmhdynrrcwxf84eldk3f94dkv7ttsrulrz5/tinyrelay.git

Refs