From 0248c86078edc09fa396f5e1bfc8bacc8fca3fd1 Mon Sep 17 00:00:00 2001 From: Mrs4s Date: Thu, 1 Dec 2022 18:09:18 +0800 Subject: [PATCH] fix #1733 --- coolq/cqcode/element.go | 4 +- coolq/event.go | 6 +- global/quote.go | 146 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 153 insertions(+), 3 deletions(-) create mode 100644 global/quote.go diff --git a/coolq/cqcode/element.go b/coolq/cqcode/element.go index 2605d5aa8..2788ba27e 100644 --- a/coolq/cqcode/element.go +++ b/coolq/cqcode/element.go @@ -2,10 +2,10 @@ package cqcode import ( "bytes" - "strconv" "strings" "github.com/Mrs4s/MiraiGo/binary" + "github.com/Mrs4s/go-cqhttp/global" ) // Element single message @@ -60,7 +60,7 @@ func (e *Element) MarshalJSON() ([]byte, error) { buf.WriteByte('"') buf.WriteString(data.K) buf.WriteString(`":`) - buf.WriteString(strconv.Quote(data.V)) + buf.WriteString(global.Quote(data.V)) } buf.WriteString(`}}`) }), nil diff --git a/coolq/event.go b/coolq/event.go index 34500e2da..02c4da624 100644 --- a/coolq/event.go +++ b/coolq/event.go @@ -61,7 +61,11 @@ func (ev *event) MarshalJSON() ([]byte, error) { fmt.Fprintf(buf, `,"sub_type":"%s"`, ev.SubType) } for k, v := range ev.Others { - v, _ := json.Marshal(v) + v, err := json.Marshal(v) + if err != nil { + log.Warnf("marshal message payload error: %v", err) + return nil, err + } fmt.Fprintf(buf, `,"%s":%s`, k, v) } buf.WriteByte('}') diff --git a/global/quote.go b/global/quote.go new file mode 100644 index 000000000..bad54b94d --- /dev/null +++ b/global/quote.go @@ -0,0 +1,146 @@ +package global + +import ( + "strconv" + "unicode/utf8" +) + +const ( + lowerhex = "0123456789abcdef" + upperhex = "0123456789ABCDEF" +) + +// Quote returns a double-quoted Go string literal representing s. The +// returned string uses Go escape sequences (\t, \n, \xFF, \u0100) for +// control characters and non-printable characters as defined by +// IsPrint. +func Quote(s string) string { + return quoteWith(s, '"', false, false) +} + +func quoteWith(s string, quote byte, ASCIIonly, graphicOnly bool) string { + return string(appendQuotedWith(make([]byte, 0, 3*len(s)/2), s, quote, ASCIIonly, graphicOnly)) +} + +func appendQuotedWith(buf []byte, s string, quote byte, ASCIIonly, graphicOnly bool) []byte { + // Often called with big strings, so preallocate. If there's quoting, + // this is conservative but still helps a lot. + if cap(buf)-len(buf) < len(s) { + nBuf := make([]byte, len(buf), len(buf)+1+len(s)+1) + copy(nBuf, buf) + buf = nBuf + } + buf = append(buf, quote) + for width := 0; len(s) > 0; s = s[width:] { + r := rune(s[0]) + width = 1 + if r >= utf8.RuneSelf { + r, width = utf8.DecodeRuneInString(s) + } + if width == 1 && r == utf8.RuneError { + buf = append(buf, `\x`...) + buf = append(buf, lowerhex[s[0]>>4]) + buf = append(buf, lowerhex[s[0]&0xF]) + continue + } + buf = appendEscapedRune(buf, r, quote, ASCIIonly, graphicOnly) + } + buf = append(buf, quote) + return buf +} +func appendEscapedRune(buf []byte, r rune, quote byte, ASCIIonly, graphicOnly bool) []byte { + var runeTmp [utf8.UTFMax]byte + if r == rune(quote) || r == '\\' { // always backslashed + buf = append(buf, '\\') + buf = append(buf, byte(r)) + return buf + } + if ASCIIonly { + if r < utf8.RuneSelf && strconv.IsPrint(r) { + buf = append(buf, byte(r)) + return buf + } + } else if strconv.IsPrint(r) || graphicOnly && isInGraphicList(r) { + n := utf8.EncodeRune(runeTmp[:], r) + buf = append(buf, runeTmp[:n]...) + return buf + } + switch r { + case '\a': + buf = append(buf, `\a`...) + case '\b': + buf = append(buf, `\b`...) + case '\f': + buf = append(buf, `\f`...) + case '\n': + buf = append(buf, `\n`...) + case '\r': + buf = append(buf, `\r`...) + case '\t': + buf = append(buf, `\t`...) + case '\v': + buf = append(buf, `\v`...) + default: + switch { + case !utf8.ValidRune(r): + r = 0xFFFD + fallthrough + case r < 0x10000: + buf = append(buf, `\u`...) + for s := 12; s >= 0; s -= 4 { + buf = append(buf, lowerhex[r>>uint(s)&0xF]) + } + default: + buf = append(buf, `\U`...) + for s := 28; s >= 0; s -= 4 { + buf = append(buf, lowerhex[r>>uint(s)&0xF]) + } + } + } + return buf +} + +func isInGraphicList(r rune) bool { + // We know r must fit in 16 bits - see makeisprint.go. + if r > 0xFFFF { + return false + } + rr := uint16(r) + i := bsearch16(isGraphic, rr) + return i < len(isGraphic) && rr == isGraphic[i] +} + +// bsearch16 returns the smallest i such that a[i] >= x. +// If there is no such i, bsearch16 returns len(a). +func bsearch16(a []uint16, x uint16) int { + i, j := 0, len(a) + for i < j { + h := i + (j-i)>>1 + if a[h] < x { + i = h + 1 + } else { + j = h + } + } + return i +} + +// isGraphic lists the graphic runes not matched by IsPrint. +var isGraphic = []uint16{ + 0x00a0, + 0x1680, + 0x2000, + 0x2001, + 0x2002, + 0x2003, + 0x2004, + 0x2005, + 0x2006, + 0x2007, + 0x2008, + 0x2009, + 0x200a, + 0x202f, + 0x205f, + 0x3000, +}