From fcd1a0f2dee45ded2a03278e9f8a0ba1bdbf79f4 Mon Sep 17 00:00:00 2001 From: Max Date: Sun, 9 Apr 2023 21:25:12 +0800 Subject: [PATCH] [add] yao.wework.* processes --- main.go | 1 + wework/process.go | 31 ++++++++ wework/wework.go | 81 +++++++++++++++++++ wework/wework_test.go | 61 ++++++++++++++ wework/xml.go | 180 ++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 354 insertions(+) create mode 100644 wework/process.go create mode 100644 wework/wework.go create mode 100644 wework/wework_test.go create mode 100644 wework/xml.go diff --git a/main.go b/main.go index b9bed45a38..32f2caf22a 100644 --- a/main.go +++ b/main.go @@ -8,6 +8,7 @@ import ( _ "github.com/yaoapp/yao/helper" _ "github.com/yaoapp/yao/openai" _ "github.com/yaoapp/yao/utils" + _ "github.com/yaoapp/yao/wework" ) // 主程序 diff --git a/wework/process.go b/wework/process.go new file mode 100644 index 0000000000..d43b1071e1 --- /dev/null +++ b/wework/process.go @@ -0,0 +1,31 @@ +package wework + +import ( + "github.com/yaoapp/gou/process" + "github.com/yaoapp/kun/exception" +) + +func init() { + process.RegisterGroup("yao.wework", map[string]process.Handler{ + "decrypt": processDecrypt, + }) +} + +func processDecrypt(process *process.Process) interface{} { + + process.ValidateArgNums(2) + encodingAESKey := process.ArgsString(0) + msgEncrypt := process.ArgsString(1) + parseXML := false + + if process.NumOfArgsIs(3) { + parseXML = process.ArgsBool(2) + } + + res, err := Decrypt(encodingAESKey, msgEncrypt, parseXML) + if err != nil { + exception.New("error: %s msgEncrypt: %s", 400, err, msgEncrypt).Throw() + } + + return res +} diff --git a/wework/wework.go b/wework/wework.go new file mode 100644 index 0000000000..8009ca14c2 --- /dev/null +++ b/wework/wework.go @@ -0,0 +1,81 @@ +package wework + +import ( + "bytes" + "crypto/aes" + "crypto/cipher" + "encoding/base64" + "encoding/binary" + "strings" +) + +// Decrypt wework msg Decrypt +func Decrypt(encodingAESKey string, msgEncrypt string, parse bool) (map[string]interface{}, error) { + + var err error + aseKey, err := base64.StdEncoding.DecodeString(encodingAESKey + "=") + if err != nil { + return nil, err + } + + ciphertext, err := base64.StdEncoding.DecodeString(msgEncrypt) + if err != nil { + return nil, err + } + + randMsg, err := aesDecrypt(ciphertext, aseKey) + if err != nil { + return nil, err + } + + content := randMsg[16:] + buf := bytes.NewBuffer(content[0:4]) + var len int32 + binary.Read(buf, binary.BigEndian, &len) + msg := content[4 : len+4] + receiveid := content[len+4:] + + data := map[string]interface{}{} + if parse { + data, err = parseXML(string(msg)) + if err != nil { + return nil, err + } + } + + return map[string]interface{}{ + "message": string(msg), + "data": data, + "receiveid": string(receiveid), + }, nil +} + +func parseXML(data string) (map[string]interface{}, error) { + + decoder := NewDecoder(strings.NewReader(data)) + result, err := decoder.Decode() + if err != nil { + return nil, err + } + return result, nil +} + +func aesDecrypt(crypted, key []byte) ([]byte, error) { + block, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + + blockSize := block.BlockSize() + blockMode := cipher.NewCBCDecrypter(block, key[:blockSize]) + origData := make([]byte, len(crypted)) + blockMode.CryptBlocks(origData, crypted) + origData = pckS5UnPadding(origData) + return origData, nil +} + +func pckS5UnPadding(origData []byte) []byte { + length := len(origData) + unpadding := int(origData[length-1]) + return origData[:(length - unpadding)] +} diff --git a/wework/wework_test.go b/wework/wework_test.go new file mode 100644 index 0000000000..55985a7cf3 --- /dev/null +++ b/wework/wework_test.go @@ -0,0 +1,61 @@ +package wework + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/yaoapp/gou/process" + "github.com/yaoapp/kun/maps" +) + +func TestWework(t *testing.T) { + + msgEncrypt := "meqbMyPr58hNy0j0YDdG9UT60UJZSh/tb3KOZt3z2SCKr6uvmSLbEnUCM89iFXS0BLWn11FOrD/xXsGUlVUSBw==" + encodingAESKey := "RhH75tStMzrH8bMxkTw8BrBfr0ZWULL5himUaRWCs7H" + + res, err := Decrypt(encodingAESKey, msgEncrypt, false) + if err != nil { + t.Fatal(err) + } + + assert.Equal(t, "8446271472585838141", res["message"]) + assert.Equal(t, "wwe146299c731e6301", res["receiveid"]) +} + +func TestWeworkProcess(t *testing.T) { + + msgEncrypt := "meqbMyPr58hNy0j0YDdG9UT60UJZSh/tb3KOZt3z2SCKr6uvmSLbEnUCM89iFXS0BLWn11FOrD/xXsGUlVUSBw==" + encodingAESKey := "RhH75tStMzrH8bMxkTw8BrBfr0ZWULL5himUaRWCs7H" + + args := []interface{}{encodingAESKey, msgEncrypt} + res := process.New("yao.wework.Decrypt", args...).Run().(map[string]interface{}) + + assert.Equal(t, "8446271472585838141", res["message"]) + assert.Equal(t, "wwe146299c731e6301", res["receiveid"]) +} + +func TestWeworkParseXML(t *testing.T) { + + xml := ` + + + + 1409659813 + + + 4561255354251345929 + 218 + + 111 + + ` + + data, err := parseXML(xml) + if err != nil { + t.Fatal(err) + } + + res := maps.Of(data).Dot() + assert.Equal(t, "218", res.Get("xml.AgentID")) + assert.Equal(t, "111", res.Get("xml.Nest.Id")) +} diff --git a/wework/xml.go b/wework/xml.go new file mode 100644 index 0000000000..184f7a4773 --- /dev/null +++ b/wework/xml.go @@ -0,0 +1,180 @@ +package wework + +import ( + "encoding/xml" + "errors" + "fmt" + "io" + "path" + "strings" +) + +const ( + attrPrefix = "@" + textPrefix = "#text" +) + +var ( + //ErrInvalidDocument invalid document err + ErrInvalidDocument = errors.New("invalid document") + + //ErrInvalidRoot data at the root level is invalid err + ErrInvalidRoot = errors.New("data at the root level is invalid") +) + +type node struct { + Parent *node + Value map[string]interface{} + Attrs []xml.Attr + Label string + Space string + Text string + HasMany bool +} + +// Decoder instance +type Decoder struct { + r io.Reader + attrPrefix string + textPrefix string +} + +// NewDecoder create new decoder instance +func NewDecoder(reader io.Reader) *Decoder { + return NewDecoderWithPrefix(reader, attrPrefix, textPrefix) +} + +// NewDecoderWithPrefix create new decoder instance with custom attribute prefix and text prefix +func NewDecoderWithPrefix(reader io.Reader, attrPrefix, textPrefix string) *Decoder { + return &Decoder{r: reader, attrPrefix: attrPrefix, textPrefix: textPrefix} +} + +// Decode xml string to map[string]interface{} +func (d *Decoder) Decode() (map[string]interface{}, error) { + decoder := xml.NewDecoder(d.r) + n := &node{} + stack := make([]*node, 0) + + for { + token, err := decoder.Token() + if err != nil && err != io.EOF { + return nil, err + } + + if token == nil { + break + } + + switch tok := token.(type) { + case xml.StartElement: + { + label := tok.Name.Local + if tok.Name.Space != "" { + label = fmt.Sprintf("%s:%s", strings.ToLower(path.Base(tok.Name.Space)), tok.Name.Local) + } + n = &node{ + Label: label, + Space: tok.Name.Space, + Parent: n, + Value: map[string]interface{}{label: map[string]interface{}{}}, + Attrs: tok.Attr, + } + + setAttrs(n, &tok, d.attrPrefix) + stack = append(stack, n) + + if n.Parent != nil { + n.Parent.HasMany = true + } + } + + case xml.CharData: + data := strings.TrimSpace(string(tok)) + if len(stack) > 0 { + stack[len(stack)-1].Text = data + } else if len(data) > 0 { + return nil, ErrInvalidRoot + } + + case xml.EndElement: + { + length := len(stack) + stack, n = stack[:length-1], stack[length-1] + + if !n.HasMany { + if len(n.Attrs) > 0 { + m := n.Value[n.Label].(map[string]interface{}) + m[d.textPrefix] = n.Text + } else { + n.Value[n.Label] = n.Text + } + } + + if len(stack) == 0 { + return n.Value, nil + } + + setNodeValue(n) + n = n.Parent + } + } + } + + return nil, ErrInvalidDocument +} + +func setAttrs(n *node, tok *xml.StartElement, attrPrefix string) { + if len(tok.Attr) > 0 { + m := make(map[string]interface{}) + for _, attr := range tok.Attr { + if len(attr.Name.Space) > 0 { + m[attrPrefix+attr.Name.Space+":"+attr.Name.Local] = attr.Value + } else { + m[attrPrefix+attr.Name.Local] = attr.Value + } + } + n.Value[tok.Name.Local] = m + } +} + +func setNodeValue(n *node) { + if v, ok := n.Parent.Value[n.Parent.Label]; ok { + m := v.(map[string]interface{}) + if v, ok = m[n.Label]; ok { + switch item := v.(type) { + case string: + m[n.Label] = []string{item, n.Value[n.Label].(string)} + case []string: + m[n.Label] = append(item, n.Value[n.Label].(string)) + case map[string]interface{}: + vm := getMap(n) + if vm != nil { + m[n.Label] = []map[string]interface{}{item, vm} + } + case []map[string]interface{}: + vm := getMap(n) + if vm != nil { + m[n.Label] = append(item, vm) + } + } + } else { + m[n.Label] = n.Value[n.Label] + } + + } else { + n.Parent.Value[n.Parent.Label] = n.Value[n.Label] + } +} + +func getMap(node *node) map[string]interface{} { + if v, ok := node.Value[node.Label]; ok { + switch v.(type) { + case string: + return map[string]interface{}{node.Label: v} + case map[string]interface{}: + return node.Value[node.Label].(map[string]interface{}) + } + } + + return nil +}