mirror of
https://github.com/kovidgoyal/kitty
synced 2026-06-08 22:28:24 +02:00
Finish parsing of FTC
This commit is contained in:
@@ -17,15 +17,18 @@ import (
|
||||
|
||||
var _ = fmt.Print
|
||||
|
||||
type Field interface {
|
||||
type Serializable interface {
|
||||
IsDefault() bool
|
||||
Serialize() string
|
||||
String() string
|
||||
}
|
||||
type Unserializable interface {
|
||||
Unserialize(string) error
|
||||
}
|
||||
|
||||
type Action int
|
||||
|
||||
func (self Action) IsDefault() bool { return self == Action_invalid }
|
||||
func (self Action) Serialize() string {
|
||||
func (self Action) String() string {
|
||||
switch self {
|
||||
default:
|
||||
return "invalid"
|
||||
@@ -47,6 +50,31 @@ func (self Action) Serialize() string {
|
||||
return "finish"
|
||||
}
|
||||
}
|
||||
func (self *Action) Unserialize(x string) (err error) {
|
||||
switch x {
|
||||
case "invalid":
|
||||
*self = Action_invalid
|
||||
case "file":
|
||||
*self = Action_file
|
||||
case "data":
|
||||
*self = Action_data
|
||||
case "end_data":
|
||||
*self = Action_end_data
|
||||
case "receive":
|
||||
*self = Action_receive
|
||||
case "send":
|
||||
*self = Action_send
|
||||
case "cancel":
|
||||
*self = Action_cancel
|
||||
case "status":
|
||||
*self = Action_status
|
||||
case "finish":
|
||||
*self = Action_finish
|
||||
default:
|
||||
err = fmt.Errorf("Unknown Action value: %#v", x)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
const (
|
||||
Action_invalid Action = iota
|
||||
@@ -68,7 +96,7 @@ const (
|
||||
)
|
||||
|
||||
func (self Compression) IsDefault() bool { return self == Compression_none }
|
||||
func (self Compression) Serialize() string {
|
||||
func (self Compression) String() string {
|
||||
switch self {
|
||||
default:
|
||||
return "none"
|
||||
@@ -76,6 +104,17 @@ func (self Compression) Serialize() string {
|
||||
return "zlib"
|
||||
}
|
||||
}
|
||||
func (self *Compression) Unserialize(x string) (err error) {
|
||||
switch x {
|
||||
case "none":
|
||||
*self = Compression_none
|
||||
case "zlib":
|
||||
*self = Compression_zlib
|
||||
default:
|
||||
err = fmt.Errorf("Unknown Compression value: %#v", x)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type FileType int
|
||||
|
||||
@@ -114,22 +153,8 @@ func (self FileType) Color() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (self FileType) String() string {
|
||||
switch self {
|
||||
case FileType_regular:
|
||||
return "FileType.Regular"
|
||||
case FileType_directory:
|
||||
return "FileType.Directory"
|
||||
case FileType_symlink:
|
||||
return "FileType.SymbolicLink"
|
||||
case FileType_link:
|
||||
return "FileType.Link"
|
||||
}
|
||||
return "FileType.Unknown"
|
||||
}
|
||||
|
||||
func (self FileType) IsDefault() bool { return self == FileType_regular }
|
||||
func (self FileType) Serialize() string {
|
||||
func (self FileType) String() string {
|
||||
switch self {
|
||||
default:
|
||||
return "regular"
|
||||
@@ -141,6 +166,21 @@ func (self FileType) Serialize() string {
|
||||
return "link"
|
||||
}
|
||||
}
|
||||
func (self *FileType) Unserialize(x string) (err error) {
|
||||
switch x {
|
||||
case "regular":
|
||||
*self = FileType_regular
|
||||
case "directory":
|
||||
*self = FileType_directory
|
||||
case "symlink":
|
||||
*self = FileType_symlink
|
||||
case "link":
|
||||
*self = FileType_link
|
||||
default:
|
||||
err = fmt.Errorf("Unknown FileType value: %#v", x)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type TransmissionType int
|
||||
|
||||
@@ -150,7 +190,7 @@ const (
|
||||
)
|
||||
|
||||
func (self TransmissionType) IsDefault() bool { return self == TransmissionType_simple }
|
||||
func (self TransmissionType) Serialize() string {
|
||||
func (self TransmissionType) String() string {
|
||||
switch self {
|
||||
default:
|
||||
return "simple"
|
||||
@@ -158,6 +198,17 @@ func (self TransmissionType) Serialize() string {
|
||||
return "rsync"
|
||||
}
|
||||
}
|
||||
func (self *TransmissionType) Unserialize(x string) (err error) {
|
||||
switch x {
|
||||
case "simple":
|
||||
*self = TransmissionType_simple
|
||||
case "rsync":
|
||||
*self = TransmissionType_rsync
|
||||
default:
|
||||
err = fmt.Errorf("Unknown TransmissionType value: %#v", x)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type QuietLevel int
|
||||
|
||||
@@ -168,7 +219,7 @@ const (
|
||||
)
|
||||
|
||||
func (self QuietLevel) IsDefault() bool { return self == Quiet_none }
|
||||
func (self QuietLevel) Serialize() string {
|
||||
func (self QuietLevel) String() string {
|
||||
switch self {
|
||||
default:
|
||||
return "0"
|
||||
@@ -178,95 +229,119 @@ func (self QuietLevel) Serialize() string {
|
||||
return "2"
|
||||
}
|
||||
}
|
||||
func (self *QuietLevel) Unserialize(x string) (err error) {
|
||||
switch x {
|
||||
case "0":
|
||||
*self = Quiet_none
|
||||
case "1":
|
||||
*self = Quiet_acknowledgements
|
||||
case "2":
|
||||
*self = Quiet_errors
|
||||
default:
|
||||
err = fmt.Errorf("Unknown QuietLevel value: %#v", x)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type FileTransmissionCommand struct {
|
||||
Action Action `name:"ac"`
|
||||
Compression Compression `name:"zip"`
|
||||
Ftype FileType `name:"ft"`
|
||||
Ttype TransmissionType `name:"tt"`
|
||||
Quiet QuietLevel `name:"q"`
|
||||
Action Action `json:"ac,omitempty"`
|
||||
Compression Compression `json:"zip,omitempty"`
|
||||
Ftype FileType `json:"ft,omitempty"`
|
||||
Ttype TransmissionType `json:"tt,omitempty"`
|
||||
Quiet QuietLevel `json:"q,omitempty"`
|
||||
|
||||
Id string `name:"id"`
|
||||
File_id string `name:"fid"`
|
||||
Bypass string `name:"pw" encoding:"base64"`
|
||||
Name string `name:"n" encoding:"base64"`
|
||||
Status string `name:"st" encoding:"base64"`
|
||||
Parent string `name:"pr"`
|
||||
Mtime time.Duration `name:"mod"`
|
||||
Permissions fs.FileMode `name:"prm"`
|
||||
Size uint64 `name:"sz"`
|
||||
Id string `json:"id,omitempty"`
|
||||
File_id string `json:"fid,omitempty"`
|
||||
Bypass string `json:"pw,omitempty" encoding:"base64"`
|
||||
Name string `json:"n,omitempty" encoding:"base64"`
|
||||
Status string `json:"st,omitempty" encoding:"base64"`
|
||||
Parent string `json:"pr,omitempty"`
|
||||
Mtime time.Duration `json:"mod,omitempty"`
|
||||
Permissions fs.FileMode `json:"prm,omitempty"`
|
||||
Size uint64 `json:"sz,omitempty"`
|
||||
|
||||
Data []byte `name:"d"`
|
||||
Data []byte `json:"d,omitempty"`
|
||||
}
|
||||
|
||||
func escape_semicolons(x string) string {
|
||||
return strings.ReplaceAll(x, ";", ";;")
|
||||
}
|
||||
|
||||
var ftc_field_map = utils.Once(func() map[string]reflect.StructField {
|
||||
ans := make(map[string]reflect.StructField)
|
||||
self := FileTransmissionCommand{}
|
||||
v := reflect.ValueOf(self)
|
||||
typ := v.Type()
|
||||
fields := reflect.VisibleFields(typ)
|
||||
for _, field := range fields {
|
||||
if name := field.Tag.Get("json"); name != "" && field.IsExported() {
|
||||
name, _, _ = strings.Cut(name, ",")
|
||||
ans[name] = field
|
||||
}
|
||||
}
|
||||
return ans
|
||||
})
|
||||
|
||||
func (self *FileTransmissionCommand) Serialize(prefix_with_osc_code ...bool) string {
|
||||
ans := strings.Builder{}
|
||||
v := reflect.ValueOf(*self)
|
||||
typ := v.Type()
|
||||
fields := reflect.VisibleFields(typ)
|
||||
found := false
|
||||
if len(prefix_with_osc_code) > 0 && prefix_with_osc_code[0] {
|
||||
ans.WriteString(strconv.Itoa(kitty.FileTransferCode))
|
||||
found = true
|
||||
}
|
||||
for _, field := range fields {
|
||||
if name := field.Tag.Get("name"); name != "" && field.IsExported() {
|
||||
val := v.FieldByIndex(field.Index)
|
||||
encoded_val := ""
|
||||
switch val.Kind() {
|
||||
case reflect.String:
|
||||
if sval := val.String(); sval != "" {
|
||||
enc := field.Tag.Get("encoding")
|
||||
switch enc {
|
||||
case "base64":
|
||||
encoded_val = base64.RawStdEncoding.EncodeToString(utils.UnsafeStringToBytes(sval))
|
||||
default:
|
||||
encoded_val = escape_semicolons(wcswidth.StripEscapeCodes(sval))
|
||||
for name, field := range ftc_field_map() {
|
||||
val := v.FieldByIndex(field.Index)
|
||||
encoded_val := ""
|
||||
switch val.Kind() {
|
||||
case reflect.String:
|
||||
if sval := val.String(); sval != "" {
|
||||
enc := field.Tag.Get("encoding")
|
||||
switch enc {
|
||||
case "base64":
|
||||
encoded_val = base64.RawStdEncoding.EncodeToString(utils.UnsafeStringToBytes(sval))
|
||||
default:
|
||||
encoded_val = escape_semicolons(wcswidth.StripEscapeCodes(sval))
|
||||
}
|
||||
}
|
||||
case reflect.Slice:
|
||||
switch val.Type().Elem().Kind() {
|
||||
case reflect.Uint8:
|
||||
if bval := val.Bytes(); len(bval) > 0 {
|
||||
encoded_val = base64.RawStdEncoding.EncodeToString(bval)
|
||||
}
|
||||
}
|
||||
case reflect.Uint64:
|
||||
if uival := val.Uint(); uival != 0 {
|
||||
encoded_val = strconv.FormatUint(uival, 10)
|
||||
}
|
||||
default:
|
||||
if val.CanInterface() {
|
||||
switch field := val.Interface().(type) {
|
||||
case Serializable:
|
||||
if !field.IsDefault() {
|
||||
encoded_val = field.String()
|
||||
}
|
||||
}
|
||||
case reflect.Slice:
|
||||
switch val.Type().Elem().Kind() {
|
||||
case reflect.Uint8:
|
||||
if bval := val.Bytes(); len(bval) > 0 {
|
||||
encoded_val = base64.RawStdEncoding.EncodeToString(bval)
|
||||
case time.Duration:
|
||||
if field != 0 {
|
||||
encoded_val = strconv.FormatInt(int64(field), 10)
|
||||
}
|
||||
}
|
||||
case reflect.Uint64:
|
||||
if uival := val.Uint(); uival != 0 {
|
||||
encoded_val = strconv.FormatUint(uival, 10)
|
||||
}
|
||||
default:
|
||||
if val.CanInterface() {
|
||||
switch field := val.Interface().(type) {
|
||||
case Field:
|
||||
if !field.IsDefault() {
|
||||
encoded_val = field.Serialize()
|
||||
}
|
||||
case time.Duration:
|
||||
if field != 0 {
|
||||
encoded_val = strconv.FormatInt(int64(field), 10)
|
||||
}
|
||||
case fs.FileMode:
|
||||
if field = field.Perm(); field != 0 {
|
||||
encoded_val = strconv.FormatInt(int64(field), 10)
|
||||
}
|
||||
case fs.FileMode:
|
||||
if field = field.Perm(); field != 0 {
|
||||
encoded_val = strconv.FormatInt(int64(field), 10)
|
||||
}
|
||||
}
|
||||
}
|
||||
if encoded_val != "" {
|
||||
if found {
|
||||
ans.WriteString(";")
|
||||
} else {
|
||||
found = true
|
||||
}
|
||||
ans.WriteString(name)
|
||||
ans.WriteString("=")
|
||||
ans.WriteString(encoded_val)
|
||||
}
|
||||
if encoded_val != "" {
|
||||
if found {
|
||||
ans.WriteString(";")
|
||||
} else {
|
||||
found = true
|
||||
}
|
||||
ans.WriteString(name)
|
||||
ans.WriteString("=")
|
||||
ans.WriteString(encoded_val)
|
||||
}
|
||||
}
|
||||
return ans.String()
|
||||
@@ -276,9 +351,70 @@ func NewFileTransmissionCommand(serialized string) (ans *FileTransmissionCommand
|
||||
ans = &FileTransmissionCommand{}
|
||||
key_length, key_start, val_start, val_length := 0, 0, 0, 0
|
||||
has_semicolons := false
|
||||
field_map := ftc_field_map()
|
||||
v := reflect.Indirect(reflect.ValueOf(ans))
|
||||
|
||||
handle_value := func(key, serialized_val string, has_semicolons bool) error {
|
||||
return nil
|
||||
if field, ok := field_map[key]; ok {
|
||||
val := v.FieldByIndex(field.Index)
|
||||
switch val.Kind() {
|
||||
case reflect.String:
|
||||
switch field.Tag.Get("encoding") {
|
||||
case "base64":
|
||||
b, err := base64.RawStdEncoding.DecodeString(serialized_val)
|
||||
if err != nil {
|
||||
return fmt.Errorf("The field %#v has invalid base64 encoded value with error: %w", key, err)
|
||||
}
|
||||
val.SetString(utils.UnsafeBytesToString(b))
|
||||
default:
|
||||
if has_semicolons {
|
||||
serialized_val = strings.ReplaceAll(serialized_val, `;;`, `;`)
|
||||
}
|
||||
val.SetString(serialized_val)
|
||||
}
|
||||
case reflect.Slice:
|
||||
switch val.Type().Elem().Kind() {
|
||||
case reflect.Uint8:
|
||||
b, err := base64.RawStdEncoding.DecodeString(serialized_val)
|
||||
if err != nil {
|
||||
return fmt.Errorf("The field %#v has invalid base64 encoded value with error: %w", key, err)
|
||||
}
|
||||
val.SetBytes(b)
|
||||
}
|
||||
case reflect.Uint64:
|
||||
b, err := strconv.ParseUint(serialized_val, 10, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("The field %#v has invalid unsigned integer value with error: %w", key, err)
|
||||
}
|
||||
val.SetUint(b)
|
||||
default:
|
||||
if val.CanAddr() {
|
||||
switch field := val.Addr().Interface().(type) {
|
||||
case Unserializable:
|
||||
err = field.Unserialize(serialized_val)
|
||||
if err != nil {
|
||||
return fmt.Errorf("The field %#v has invalid enum value with error: %w", key, err)
|
||||
}
|
||||
case *time.Duration:
|
||||
b, err := strconv.ParseInt(serialized_val, 10, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("The field %#v has invalid time value with error: %w", key, err)
|
||||
}
|
||||
*field = time.Duration(b)
|
||||
case *fs.FileMode:
|
||||
b, err := strconv.ParseUint(serialized_val, 10, 32)
|
||||
if err != nil {
|
||||
return fmt.Errorf("The field %#v has invalid file mode value with error: %w", key, err)
|
||||
}
|
||||
*field = fs.FileMode(b).Perm()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return nil
|
||||
} else {
|
||||
return fmt.Errorf("The field name %#v is not known", key)
|
||||
}
|
||||
}
|
||||
|
||||
for i := 0; i < len(serialized); i++ {
|
||||
@@ -296,16 +432,25 @@ func NewFileTransmissionCommand(serialized string) (ans *FileTransmissionCommand
|
||||
i++
|
||||
} else {
|
||||
val_length = i - val_start
|
||||
err = handle_value(serialized[key_start:key_start+key_length], serialized[val_start:val_start+val_length], has_semicolons)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if key_length > 0 && val_start > 0 {
|
||||
err = handle_value(serialized[key_start:key_start+key_length], serialized[val_start:val_start+val_length], has_semicolons)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
key_length = 0
|
||||
key_start = i + 1
|
||||
val_start = 0
|
||||
val_length = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if key_length > 0 && val_start > 0 {
|
||||
err = handle_value(serialized[key_start:key_start+key_length], serialized[val_start:], has_semicolons)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ package transfer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -16,7 +17,15 @@ func TestFTCSerialization(t *testing.T) {
|
||||
ftc := FileTransmissionCommand{}
|
||||
q := func(expected string) {
|
||||
actual := ftc.Serialize()
|
||||
if diff := cmp.Diff(expected, actual); diff != "" {
|
||||
ad := make(map[string]bool)
|
||||
for _, x := range strings.Split(actual, ";") {
|
||||
ad[x] = true
|
||||
}
|
||||
ed := make(map[string]bool)
|
||||
for _, x := range strings.Split(expected, ";") {
|
||||
ed[x] = true
|
||||
}
|
||||
if diff := cmp.Diff(ed, ad); diff != "" {
|
||||
t.Fatalf("Failed to Serialize:\n%s", diff)
|
||||
}
|
||||
}
|
||||
@@ -29,4 +38,9 @@ func TestFTCSerialization(t *testing.T) {
|
||||
ftc.Permissions = 0o600
|
||||
ftc.Data = []byte("moose")
|
||||
q("ac=send;fid=fid;n=bW9vc2U;mod=1000000000;prm=384;d=bW9vc2U")
|
||||
n, err := NewFileTransmissionCommand(ftc.Serialize())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
q(n.Serialize())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user