Cthulhu is no longer a fork. Now it is a screen reader written in Golang.
This commit is contained in:
1 parent
a998fe67de
commit
069fc674dc
708 files changed
+112482
-532
No files matched your search
@@ -0,0 +1,106 @@
|
||||
// Copyright (c) 2026 Storm Dragon
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// Package tentaclewire shares bounded framing between the host and Go SDK.
|
||||
package tentaclewire
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
const MaxFrameBytes = 64 * 1024
|
||||
|
||||
var ErrProtocol = errors.New("invalid tentacle protocol message")
|
||||
var ErrLimit = errors.New("tentacle resource limit exceeded")
|
||||
|
||||
// ReadFrame bounds allocation even when the child never writes a newline.
|
||||
func ReadFrame(reader *bufio.Reader) ([]byte, error) {
|
||||
var frame []byte
|
||||
for {
|
||||
part, err := reader.ReadSlice('\n')
|
||||
if len(frame)+len(part) > MaxFrameBytes+1 {
|
||||
return nil, ErrLimit
|
||||
}
|
||||
frame = append(frame, part...)
|
||||
if err == nil {
|
||||
return frame[:len(frame)-1], nil
|
||||
}
|
||||
if errors.Is(err, bufio.ErrBufferFull) {
|
||||
continue
|
||||
}
|
||||
if errors.Is(err, io.EOF) && len(frame) != 0 {
|
||||
return nil, io.ErrUnexpectedEOF
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Reject duplicate keys and excessive nesting, including in command arguments
|
||||
// and results. Different languages must not interpret the same frame differently.
|
||||
func ValidateJSON(data []byte) error {
|
||||
if !utf8.Valid(data) {
|
||||
return ErrProtocol
|
||||
}
|
||||
decoder := json.NewDecoder(bytes.NewReader(data))
|
||||
decoder.UseNumber()
|
||||
if err := validate_value(decoder, 0); err != nil {
|
||||
return ErrProtocol
|
||||
}
|
||||
if _, err := decoder.Token(); err != io.EOF {
|
||||
return ErrProtocol
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validate_value(decoder *json.Decoder, depth int) error {
|
||||
if depth > 32 {
|
||||
return ErrLimit
|
||||
}
|
||||
token, err := decoder.Token()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
delimiter, composite := token.(json.Delim)
|
||||
if !composite {
|
||||
return nil
|
||||
}
|
||||
if delimiter != '{' && delimiter != '[' {
|
||||
return ErrProtocol
|
||||
}
|
||||
keys := map[string]bool{}
|
||||
for decoder.More() {
|
||||
if delimiter == '{' {
|
||||
keyToken, err := decoder.Token()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
key, ok := keyToken.(string)
|
||||
if !ok || keys[key] {
|
||||
return ErrProtocol
|
||||
}
|
||||
keys[key] = true
|
||||
}
|
||||
if err := validate_value(decoder, depth+1); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
_, err = decoder.Token()
|
||||
return err
|
||||
}
|
||||
|
||||
func DecodeStrict(data []byte, target any) error {
|
||||
if err := ValidateJSON(data); err != nil {
|
||||
return err
|
||||
}
|
||||
decoder := json.NewDecoder(bytes.NewReader(data))
|
||||
decoder.DisallowUnknownFields()
|
||||
if err := decoder.Decode(target); err != nil {
|
||||
return ErrProtocol
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in new issue
Block a user