rc protocol: Encode strings values in an escape code safe way
Go emits UTF-8 encoded JSON not ascii encoded JSON. Still need to fix lists and dicts of strings
This commit is contained in:
parent
95e05ce9ec
commit
aac57550c9
@ -167,7 +167,7 @@ def generate_completions_for_kitty() -> None:
|
|||||||
|
|
||||||
# rc command wrappers {{{
|
# rc command wrappers {{{
|
||||||
json_field_types: Dict[str, str] = {
|
json_field_types: Dict[str, str] = {
|
||||||
'bool': 'bool', 'str': 'string', 'list.str': '[]string', 'dict.str': 'map[string]string', 'float': 'float64', 'int': 'int',
|
'bool': 'bool', 'str': 'escaped_string', 'list.str': '[]string', 'dict.str': 'map[string]string', 'float': 'float64', 'int': 'int',
|
||||||
'scroll_amount': 'any', 'spacing': 'any', 'colors': 'any',
|
'scroll_amount': 'any', 'spacing': 'any', 'colors': 'any',
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -237,6 +237,9 @@ def go_code_for_remote_command(name: str, cmd: RemoteCommand, template: str) ->
|
|||||||
if oq in option_map:
|
if oq in option_map:
|
||||||
o = option_map[oq]
|
o = option_map[oq]
|
||||||
used_options.add(oq)
|
used_options.add(oq)
|
||||||
|
if field.field_type == 'str':
|
||||||
|
jc.append(f'payload.{field.struct_field_name} = escaped_string(options_{name}.{o.go_var_name})')
|
||||||
|
else:
|
||||||
jc.append(f'payload.{field.struct_field_name} = options_{name}.{o.go_var_name}')
|
jc.append(f'payload.{field.struct_field_name} = options_{name}.{o.go_var_name}')
|
||||||
elif field.field in handled_fields:
|
elif field.field in handled_fields:
|
||||||
pass
|
pass
|
||||||
@ -247,6 +250,9 @@ def go_code_for_remote_command(name: str, cmd: RemoteCommand, template: str) ->
|
|||||||
used_options.add('Match')
|
used_options.add('Match')
|
||||||
o = option_map['Match']
|
o = option_map['Match']
|
||||||
field = unhandled[x]
|
field = unhandled[x]
|
||||||
|
if field.field_type == 'str':
|
||||||
|
jc.append(f'payload.{field.struct_field_name} = escaped_string(options_{name}.{o.go_var_name})')
|
||||||
|
else:
|
||||||
jc.append(f'payload.{field.struct_field_name} = options_{name}.{o.go_var_name}')
|
jc.append(f'payload.{field.struct_field_name} = options_{name}.{o.go_var_name}')
|
||||||
del unhandled[x]
|
del unhandled[x]
|
||||||
if unhandled:
|
if unhandled:
|
||||||
|
|||||||
@ -232,7 +232,7 @@ class ArgsHandling:
|
|||||||
dest = f'payload.{jf.capitalize()}'
|
dest = f'payload.{jf.capitalize()}'
|
||||||
jt = field_types[jf]
|
jt = field_types[jf]
|
||||||
if self.first_rest:
|
if self.first_rest:
|
||||||
yield f'payload.{self.first_rest[0].capitalize()} = args[0]'
|
yield f'payload.{self.first_rest[0].capitalize()} = escaped_string(args[0])'
|
||||||
yield f'payload.{self.first_rest[1].capitalize()} = args[1:]'
|
yield f'payload.{self.first_rest[1].capitalize()} = args[1:]'
|
||||||
handled_fields.add(self.first_rest[0])
|
handled_fields.add(self.first_rest[0])
|
||||||
handled_fields.add(self.first_rest[1])
|
handled_fields.add(self.first_rest[1])
|
||||||
@ -254,9 +254,9 @@ class ArgsHandling:
|
|||||||
return
|
return
|
||||||
if jt == 'str':
|
if jt == 'str':
|
||||||
if c == 1:
|
if c == 1:
|
||||||
yield f'{dest} = args[0]'
|
yield f'{dest} = escaped_string(args[0])'
|
||||||
else:
|
else:
|
||||||
yield f'{dest} = strings.Join(args, " ")'
|
yield f'{dest} = escaped_string(strings.Join(args, " "))'
|
||||||
return
|
return
|
||||||
if jt.startswith('choices.'):
|
if jt.startswith('choices.'):
|
||||||
yield f'if len(args) != 1 {{ return fmt.Errorf("%s", "Must specify exactly 1 argument for {cmd_name}") }}'
|
yield f'if len(args) != 1 {{ return fmt.Errorf("%s", "Must specify exactly 1 argument for {cmd_name}") }}'
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import (
|
|||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
"unicode/utf16"
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
|
|
||||||
@ -27,6 +28,8 @@ import (
|
|||||||
"github.com/jamesruan/go-rfc1924/base85"
|
"github.com/jamesruan/go-rfc1924/base85"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const lowerhex = "0123456789abcdef"
|
||||||
|
|
||||||
var ProtocolVersion [3]int = [3]int{0, 26, 0}
|
var ProtocolVersion [3]int = [3]int{0, 26, 0}
|
||||||
|
|
||||||
type GlobalOptions struct {
|
type GlobalOptions struct {
|
||||||
@ -36,11 +39,11 @@ type GlobalOptions struct {
|
|||||||
|
|
||||||
var global_options GlobalOptions
|
var global_options GlobalOptions
|
||||||
|
|
||||||
func expand_ansi_c_escapes_in_args(args ...string) (string, error) {
|
func expand_ansi_c_escapes_in_args(args ...string) (escaped_string, error) {
|
||||||
for i, x := range args {
|
for i, x := range args {
|
||||||
args[i] = shlex.ExpandANSICEscapes(x)
|
args[i] = shlex.ExpandANSICEscapes(x)
|
||||||
}
|
}
|
||||||
return strings.Join(args, " "), nil
|
return escaped_string(strings.Join(args, " ")), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func set_payload_string_field(io_data *rc_io_data, field, data string) {
|
func set_payload_string_field(io_data *rc_io_data, field, data string) {
|
||||||
@ -76,6 +79,48 @@ func get_pubkey(encoded_key string) (encryption_version string, pubkey []byte, e
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type escaped_string string
|
||||||
|
|
||||||
|
func (s escaped_string) MarshalJSON() ([]byte, error) {
|
||||||
|
// See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON
|
||||||
|
// we additionally escape all non-ascii chars so they can be safely transmitted inside an escape code
|
||||||
|
src := utf16.Encode([]rune(s))
|
||||||
|
buf := make([]byte, 0, len(src)+128)
|
||||||
|
a := func(x ...byte) {
|
||||||
|
buf = append(buf, x...)
|
||||||
|
}
|
||||||
|
a('"')
|
||||||
|
for _, r := range src {
|
||||||
|
if ' ' <= r && r <= 126 {
|
||||||
|
buf = append(buf, byte(r))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
switch r {
|
||||||
|
case '\n':
|
||||||
|
a('\\', 'n')
|
||||||
|
case '\t':
|
||||||
|
a('\\', 't')
|
||||||
|
case '\r':
|
||||||
|
a('\\', 'r')
|
||||||
|
case '\f':
|
||||||
|
a('\\', 'f')
|
||||||
|
case '\b':
|
||||||
|
a('\\', 'b')
|
||||||
|
case '\\':
|
||||||
|
a('\\', '\\')
|
||||||
|
case '"':
|
||||||
|
a('\\', '"')
|
||||||
|
default:
|
||||||
|
a('\\', 'u')
|
||||||
|
for s := 12; s >= 0; s -= 4 {
|
||||||
|
a(lowerhex[r>>uint(s)&0xF])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
a('"')
|
||||||
|
return buf, nil
|
||||||
|
}
|
||||||
|
|
||||||
func simple_serializer(rc *utils.RemoteControlCmd) (ans []byte, err error) {
|
func simple_serializer(rc *utils.RemoteControlCmd) (ans []byte, err error) {
|
||||||
return json.Marshal(rc)
|
return json.Marshal(rc)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,6 +10,25 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestEncodeJSON(t *testing.T) {
|
||||||
|
tests := map[string]string{
|
||||||
|
"a b\nc\td\a": `a b\nc\td\u0007`,
|
||||||
|
"•": `\u2022`,
|
||||||
|
"\U0001f123": `\ud83c\udd23`,
|
||||||
|
}
|
||||||
|
var s escaped_string
|
||||||
|
for x, expected := range tests {
|
||||||
|
s = escaped_string(x)
|
||||||
|
expected = `"` + expected + `"`
|
||||||
|
actualb, _ := s.MarshalJSON()
|
||||||
|
actual := string(actualb)
|
||||||
|
if expected != actual {
|
||||||
|
t.Fatalf("Failed for %#v\n%#v != %#v", x, expected, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
func TestCommandToJSON(t *testing.T) {
|
func TestCommandToJSON(t *testing.T) {
|
||||||
pv := fmt.Sprint(ProtocolVersion[0], ",", ProtocolVersion[1], ",", ProtocolVersion[2])
|
pv := fmt.Sprint(ProtocolVersion[0], ",", ProtocolVersion[1], ",", ProtocolVersion[2])
|
||||||
rc, err := create_rc_ls([]string{})
|
rc, err := create_rc_ls([]string{})
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user