You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
DateKeys/extension/datekeys.go

109 lines
3.8 KiB

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
}

Powered by TurnKey Linux.