Finish parsing of FTC

This commit is contained in:
Kovid Goyal
2023-05-27 10:15:17 +05:30
parent 00a04d68cc
commit b9b7ab5221
2 changed files with 250 additions and 91 deletions

View File

@@ -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
}

View File

@@ -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())
}