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.
109 lines
3.8 KiB
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
|
|
}
|