Every value an approval sets is in the note, whole where the sender is proven; the note is kept whole (issue 383)
mesh/merge-gate out of date: judged against trunk e209d46faf3d, the trunk is now 9c166398f204; judged again on a recheck or when it is merged
mesh/repo-check out of date: judged against trunk e209d46faf3d, the trunk is now 9c166398f204; judged again on a recheck or when it is merged
mesh/delivery superseded: a newer head of the same pull request
mesh/merge-gate out of date: judged against trunk e209d46faf3d, the trunk is now 9c166398f204; judged again on a recheck or when it is merged
mesh/repo-check out of date: judged against trunk e209d46faf3d, the trunk is now 9c166398f204; judged again on a recheck or when it is merged
mesh/delivery superseded: a newer head of the same pull request
The review of mesh-controller #304: a proposal of more than three keys showed only the names of the rest, and its Details could carry no value, so Approve bound values the operator never saw. The router now shows an ask's WholeDetails in the note in place of its Details on a kind that proves who answers, held to the secret rule alone, and refuses an ask whose whole details carry a secret's shape. Telegram keeps the note whole when a message is too long, cutting the body first and saying so, and escapes the note as every other part. SDK at mesh-sdk f474544.
This commit is contained in:
@@ -31,7 +31,7 @@ as the operator.
|
||||
| | fixed here |
|
||||
|---|---|
|
||||
| D1 | up to 4096 characters arrive whole; longer is cut and the cut said |
|
||||
| issues 383, 639 | shown formatted (HTML), never reworded: the title bold, the body as it is with a key line in monospace, the note apart in a blockquote collapsed to its first lines; `<`, `>` and `&` escaped first, so nothing in the words is read as markup. A tap is compared by the plain words, whitespace aside |
|
||||
| issues 383, 639 | shown formatted (HTML), never reworded: the title bold, the body as it is with a key line in monospace, the note apart in a blockquote collapsed to its first lines, kept whole when a message is too long (the body is cut first, and the cut said); `<`, `>` and `&` escaped first, so nothing in the words is read as markup. A tap is compared by the plain words, whitespace aside |
|
||||
| D4 | a warning or a clearing arrives without sound; an urgent message or a question rings |
|
||||
| D5 | a rate limit is waited out as told |
|
||||
| D6 | a refusal is a permanent failure, said to the router and not retried; an edit to the same text is a success |
|
||||
|
||||
@@ -35,8 +35,10 @@ func escape(s string) string {
|
||||
// shown is a message's three parts as the chat carries them.
|
||||
type shown struct{ title, body, note string }
|
||||
|
||||
// partsOf is a message's parts, cut so that its plain words fit MaxLength and the cut said (D1): the note is cut
|
||||
// first, then the body. The bound is on the words, as Telegram's is on the text once its markup is read.
|
||||
// partsOf is a message's parts, cut so that its plain words fit MaxLength and the cut said (D1). The body is cut
|
||||
// first: the note carries what each answer does and every value an approval sets, so it is kept whole as long as
|
||||
// the title and the note fit; only then is the note cut, and that said too. The bound is on the words, as
|
||||
// Telegram's is on the text once its markup is read.
|
||||
func partsOf(m asks.Message) shown {
|
||||
s := shown{title: strings.TrimSpace(m.Title), body: strings.TrimSpace(m.Body), note: strings.TrimSpace(m.Note)}
|
||||
over := utf8.RuneCountInString(s.plain()) - MaxLength
|
||||
@@ -44,21 +46,16 @@ func partsOf(m asks.Message) shown {
|
||||
return s
|
||||
}
|
||||
mark := utf8.RuneCountInString(cutNote)
|
||||
cut := func(text string, by int) string {
|
||||
r := []rune(text)
|
||||
keep := max(len(r)-by-mark, 0)
|
||||
return string(r[:keep]) + cutNote
|
||||
}
|
||||
if utf8.RuneCountInString(s.note) > over+mark {
|
||||
s.note = cut(s.note, over)
|
||||
if r := []rune(s.body); len(r) >= over+mark {
|
||||
s.body = string(r[:len(r)-over-mark]) + cutNote
|
||||
return s
|
||||
}
|
||||
s.note = ""
|
||||
keep := max(MaxLength-utf8.RuneCountInString(s.title)-1-mark, 0)
|
||||
if r := []rune(s.body); keep < len(r) {
|
||||
s.body = string(r[:keep])
|
||||
// The body cannot make room alone: what is left of it is the cut's word, and the note is cut too.
|
||||
s.body = strings.TrimPrefix(cutNote, "\n")
|
||||
if over = utf8.RuneCountInString(s.plain()) - MaxLength; over > 0 {
|
||||
r := []rune(s.note)
|
||||
s.note = string(r[:max(len(r)-over-mark, 0)]) + cutNote
|
||||
}
|
||||
s.body += cutNote
|
||||
return s
|
||||
}
|
||||
|
||||
|
||||
@@ -61,16 +61,26 @@ func TestTheADR0343AskFitsAGlance(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// A long note is cut before the body is, and the cut said; a body too long loses the note and is cut itself.
|
||||
func TestALongNoteIsCutBeforeTheBody(t *testing.T) {
|
||||
m := asks.Message{Title: "t", Body: "the summary", Note: strings.Repeat("n", 5000)}
|
||||
// The note is kept whole when the message is too long: the body is cut first and the cut said; only a note that
|
||||
// cannot fit beside the title is cut, and that said too (the review of mesh-catalog #278).
|
||||
func TestALongBodyIsCutBeforeTheNote(t *testing.T) {
|
||||
m := asks.Message{Title: "t", Body: strings.Repeat("b", 5000), Note: "+ shares: media=/storage/media\nFingerprint 1234."}
|
||||
s := partsOf(m)
|
||||
if s.body != "the summary" || !strings.HasSuffix(s.note, cutNote) || len([]rune(s.plain())) != MaxLength {
|
||||
if s.note != m.Note || !strings.HasSuffix(s.body, cutNote) || len([]rune(s.plain())) != MaxLength {
|
||||
t.Errorf("note %q, body ends %q, %d characters", s.note, s.body[len(s.body)-20:], len([]rune(s.plain())))
|
||||
}
|
||||
m.Body, m.Note = "the summary", strings.Repeat("n", 5000)
|
||||
s = partsOf(m)
|
||||
if !strings.HasSuffix(s.note, cutNote) || !strings.Contains(s.body, "cut here") || len([]rune(s.plain())) != MaxLength {
|
||||
t.Errorf("body %q, note ends %q, %d characters", s.body, s.note[len(s.note)-20:], len([]rune(s.plain())))
|
||||
}
|
||||
m.Body = strings.Repeat("b", 5000)
|
||||
s = partsOf(m)
|
||||
if s.note != "" || !strings.HasSuffix(s.body, cutNote) || len([]rune(s.plain())) != MaxLength {
|
||||
t.Errorf("note %d, %d characters", len(s.note), len([]rune(s.plain())))
|
||||
}
|
||||
|
||||
// A '<', '>' or '&' in the note is escaped like any other part: it neither breaks the blockquote nor injects.
|
||||
func TestTheNoteIsEscaped(t *testing.T) {
|
||||
got := partsOf(asks.Message{Title: "t", Note: "Asked by <b>x</b> & y </blockquote><i>"}).html()
|
||||
want := "<b>t</b>\n\n<blockquote expandable>Asked by <b>x</b> & y </blockquote><i></blockquote>"
|
||||
if got != want {
|
||||
t.Errorf("%q", got)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,4 +2,4 @@ module telegram
|
||||
|
||||
go 1.22
|
||||
|
||||
require git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261013141520-9588871d6629
|
||||
require git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261013145622-f4745447ba6d
|
||||
|
||||
@@ -2,3 +2,5 @@ git.novox.be/novox/mesh-sdk/go v0.1.10 h1:EHhnPRrkw7/CSQAqRsxYnTvlTZ3dI02IPO6WSm
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.10/go.mod h1:GFuZUElBZ9A++mxgIKo97aXXo+kV0uJ/UkbhQPPIbrY=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261013141520-9588871d6629 h1:7TYFV1Q6Mcz+lsLP/sds87tysET4rKCn3rRIQV8gNmA=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261013141520-9588871d6629/go.mod h1:GFuZUElBZ9A++mxgIKo97aXXo+kV0uJ/UkbhQPPIbrY=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261013145622-f4745447ba6d h1:HEJyqorKktrkJPeeJdwEo0kdrJjH3fm/YVS9MGftCqY=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261013145622-f4745447ba6d/go.mod h1:GFuZUElBZ9A++mxgIKo97aXXo+kV0uJ/UkbhQPPIbrY=
|
||||
|
||||
Reference in New Issue
Block a user