package extension import ( "fmt" "unicode/utf8" datekeys "g.activething.com/go/DateKeys" "g.activething.com/go/DateKeys/internal/pathrule" ) // The extensions that spec v0.11 registers (§72). const ( // NoteID is the public note of a capsule, in the noncritical array of // PUBLIC_HEADER (§24.1), version 1: its data is the text in UTF-8, with // no CBOR around it. NoteID = "datekeys.note" // CapsuleID is the extension of a .dkk that says what its capsule is and // where to find it (§44.1), version 1. CapsuleID = "datekeys.capsule" // MaxNoteLen is the longest public note, in bytes. MaxNoteLen = 1024 ) // CheckNote checks the text of a public note with the rules of spec §24.1: // from 1 to 1024 bytes of valid UTF-8 that meet the rules of the declared // author of §29.6, one line without tabs and without spaces at the ends. func CheckNote(text string) error { switch { case text == "" || len(text) > MaxNoteLen: return fmt.Errorf("a public note of %d bytes, not 1 to %d: %w", len(text), MaxNoteLen, datekeys.ErrExtensionDataInvalid) case !utf8.ValidString(text): return fmt.Errorf("a public note that is not valid UTF-8: %w", datekeys.ErrExtensionDataInvalid) } if err := pathrule.CheckAuthor(text); err != nil { return fmt.Errorf("a public note that breaks the rules of text: %v: %w", err, datekeys.ErrExtensionDataInvalid) } return nil } // NewNote returns the public note extension for text, which must pass // CheckNote. A note is public: whoever has the .dkc reads it before the date, // and with the date it can identify someone (spec §24.1). func NewNote(text string) (Extension, error) { if err := CheckNote(text); err != nil { return Extension{}, err } return New(NoteID, 1, []byte(text)) } // Note returns the text of the public note among the noncritical extensions // of a PUBLIC_HEADER, and whether there is one that is usable: "", false when // there is none, or when its data breaks the rules of §24.1, in which case // a reader treats it as unusable and shows nothing (spec §54). func Note(noncritical []Extension) (string, bool) { for _, e := range noncritical { if e.ID == NoteID && e.Version == 1 { if e.Data == nil || CheckNote(string(e.Data)) != nil { return "", false } return string(e.Data), true } } return "", false } // Standard is the Registry of the extensions that spec v0.11 defines: the // public note in PUBLIC_HEADER and datekeys.capsule in a .dkk, both // noncritical. It validates their data, as spec §54 asks of a reader that // knows an extension, and registers each only where §72 does. type Standard struct { // ValidateCapsule checks the data of datekeys.capsule, which package // locator decodes: it cannot be imported here. locator.Standard sets it. // When nil, only the presence of the data is checked. ValidateCapsule func(Extension) error } // Known reports whether (id, version) is one of the two. func (Standard) Known(id string, version uint64) bool { return version == 1 && (id == NoteID || id == CapsuleID) } // RegisteredIn reports where the extensions are registered (spec §72). func (Standard) RegisteredIn(id string, version uint64, obj Object, arr Array) bool { if arr != Noncritical || version != 1 { return false } return id == NoteID && obj == PublicHeader || id == CapsuleID && obj == AccessKey } // ValidateData checks the data of a note, and the data of datekeys.capsule // with ValidateCapsule, when it is set. func (s Standard) ValidateData(e Extension) error { switch e.ID { case NoteID: if e.Data == nil { return fmt.Errorf("a public note without data: %w", datekeys.ErrExtensionDataInvalid) } return CheckNote(string(e.Data)) case CapsuleID: if e.Data == nil { return fmt.Errorf("datekeys.capsule without data: %w", datekeys.ErrExtensionDataInvalid) } if s.ValidateCapsule != nil { return s.ValidateCapsule(e) } } return nil }