3
0
mirror of https://github.com/ergochat/ergo.git synced 2024-11-25 13:29:27 +01:00

Merge pull request #2074 from slingamn/ircgo_upgrade

upgrade to irc-go v0.4.0
This commit is contained in:
Shivaram Lingamneni 2023-06-13 23:53:52 -07:00 committed by GitHub
commit 6d642bfe93
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 178 additions and 82 deletions

2
go.mod
View File

@ -8,7 +8,7 @@ require (
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815 github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815
github.com/ergochat/confusables v0.0.0-20201108231250-4ab98ab61fb1 github.com/ergochat/confusables v0.0.0-20201108231250-4ab98ab61fb1
github.com/ergochat/go-ident v0.0.0-20200511222032-830550b1d775 github.com/ergochat/go-ident v0.0.0-20200511222032-830550b1d775
github.com/ergochat/irc-go v0.2.0 github.com/ergochat/irc-go v0.4.0
github.com/go-sql-driver/mysql v1.7.0 github.com/go-sql-driver/mysql v1.7.0
github.com/go-test/deep v1.0.6 // indirect github.com/go-test/deep v1.0.6 // indirect
github.com/gofrs/flock v0.8.1 github.com/gofrs/flock v0.8.1

4
go.sum
View File

@ -10,8 +10,8 @@ github.com/ergochat/confusables v0.0.0-20201108231250-4ab98ab61fb1 h1:WLHTOodthV
github.com/ergochat/confusables v0.0.0-20201108231250-4ab98ab61fb1/go.mod h1:mov+uh1DPWsltdQnOdzn08UO9GsJ3MEvhtu0Ci37fdk= github.com/ergochat/confusables v0.0.0-20201108231250-4ab98ab61fb1/go.mod h1:mov+uh1DPWsltdQnOdzn08UO9GsJ3MEvhtu0Ci37fdk=
github.com/ergochat/go-ident v0.0.0-20200511222032-830550b1d775 h1:QSJIdpr3HOzJDPwxT7hp7WbjoZcS+5GqVvsBscqChk0= github.com/ergochat/go-ident v0.0.0-20200511222032-830550b1d775 h1:QSJIdpr3HOzJDPwxT7hp7WbjoZcS+5GqVvsBscqChk0=
github.com/ergochat/go-ident v0.0.0-20200511222032-830550b1d775/go.mod h1:d2qvgjD0TvGNSvUs+mZgX090RiJlrzUYW6vtANGOy3A= github.com/ergochat/go-ident v0.0.0-20200511222032-830550b1d775/go.mod h1:d2qvgjD0TvGNSvUs+mZgX090RiJlrzUYW6vtANGOy3A=
github.com/ergochat/irc-go v0.2.0 h1:3vHdy4c56UTY6+/rTBrQc1fmt32N5G8PrEZacJDOr+E= github.com/ergochat/irc-go v0.4.0 h1:0YibCKfAAtwxQdNjLQd9xpIEPisLcJ45f8FNsMHAuZc=
github.com/ergochat/irc-go v0.2.0/go.mod h1:2vi7KNpIPWnReB5hmLpl92eMywQvuIeIIGdt/FQCph0= github.com/ergochat/irc-go v0.4.0/go.mod h1:2vi7KNpIPWnReB5hmLpl92eMywQvuIeIIGdt/FQCph0=
github.com/ergochat/scram v1.0.2-ergo1 h1:2bYXiRFQH636pT0msOG39fmEYl4Eq+OuutcyDsCix/g= github.com/ergochat/scram v1.0.2-ergo1 h1:2bYXiRFQH636pT0msOG39fmEYl4Eq+OuutcyDsCix/g=
github.com/ergochat/scram v1.0.2-ergo1/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs= github.com/ergochat/scram v1.0.2-ergo1/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs=
github.com/ergochat/websocket v1.4.2-oragono1 h1:plMUunFBM6UoSCIYCKKclTdy/TkkHfUslhOfJQzfueM= github.com/ergochat/websocket v1.4.2-oragono1 h1:plMUunFBM6UoSCIYCKKclTdy/TkkHfUslhOfJQzfueM=

View File

@ -13,7 +13,7 @@ import (
"sync" "sync"
"github.com/ergochat/irc-go/ircutils" "github.com/ergochat/irc-go/ircmsg"
"github.com/ergochat/ergo/irc/caps" "github.com/ergochat/ergo/irc/caps"
"github.com/ergochat/ergo/irc/datastore" "github.com/ergochat/ergo/irc/datastore"
@ -1191,7 +1191,7 @@ func (channel *Channel) SetTopic(client *Client, topic string, rb *ResponseBuffe
return return
} }
topic = ircutils.TruncateUTF8Safe(topic, client.server.Config().Limits.TopicLen) topic = ircmsg.TruncateUTF8Safe(topic, client.server.Config().Limits.TopicLen)
channel.stateMutex.Lock() channel.stateMutex.Lock()
chname := channel.name chname := channel.name
@ -1450,7 +1450,7 @@ func (channel *Channel) Kick(client *Client, target *Client, comment string, rb
return return
} }
comment = ircutils.TruncateUTF8Safe(comment, channel.server.Config().Limits.KickLen) comment = ircmsg.TruncateUTF8Safe(comment, channel.server.Config().Limits.KickLen)
message := utils.MakeMessage(comment) message := utils.MakeMessage(comment)
details := client.Details() details := client.Details()

View File

@ -451,7 +451,7 @@ func awayHandler(server *Server, client *Client, msg ircmsg.Message, rb *Respons
var awayMessage string var awayMessage string
if len(msg.Params) > 0 { if len(msg.Params) > 0 {
awayMessage = msg.Params[0] awayMessage = msg.Params[0]
awayMessage = ircutils.TruncateUTF8Safe(awayMessage, server.Config().Limits.AwayLen) awayMessage = ircmsg.TruncateUTF8Safe(awayMessage, server.Config().Limits.AwayLen)
} }
wasAway, nowAway := rb.session.SetAway(awayMessage) wasAway, nowAway := rb.session.SetAway(awayMessage)

View File

@ -5,6 +5,7 @@ package ircfmt
import ( import (
"regexp" "regexp"
"strconv"
"strings" "strings"
) )
@ -19,24 +20,126 @@ const (
underline string = "\x1f" underline string = "\x1f"
reset string = "\x0f" reset string = "\x0f"
runecolour rune = '\x03' metacharacters = (bold + colour + monospace + reverseColour + italic + strikethrough + underline + reset)
runebold rune = '\x02'
runemonospace rune = '\x11'
runereverseColour rune = '\x16'
runeitalic rune = '\x1d'
runestrikethrough rune = '\x1e'
runereset rune = '\x0f'
runeunderline rune = '\x1f'
// valid characters in a colour code character, for speed
colours1 string = "0123456789"
) )
// ColorCode is a normalized representation of an IRC color code,
// as per this de facto specification: https://modern.ircdocs.horse/formatting.html#color
// The zero value of the type represents a default or unset color,
// whereas ColorCode{true, 0} represents the color white.
type ColorCode struct {
IsSet bool
Value uint8
}
// ParseColor converts a string representation of an IRC color code, e.g. "04",
// into a normalized ColorCode, e.g. ColorCode{true, 4}.
func ParseColor(str string) (color ColorCode) {
// "99 - Default Foreground/Background - Not universally supported."
// normalize 99 to ColorCode{} meaning "unset":
if code, err := strconv.ParseUint(str, 10, 8); err == nil && code < 99 {
color.IsSet = true
color.Value = uint8(code)
}
return
}
// FormattedSubstring represents a section of an IRC message with associated
// formatting data.
type FormattedSubstring struct {
Content string
ForegroundColor ColorCode
BackgroundColor ColorCode
Bold bool
Monospace bool
Strikethrough bool
Underline bool
Italic bool
ReverseColor bool
}
// IsFormatted returns whether the section has any formatting flags switched on.
func (f *FormattedSubstring) IsFormatted() bool {
// could rely on value receiver but if this is to be a public API,
// let's make it a pointer receiver
g := *f
g.Content = ""
return g != FormattedSubstring{}
}
var (
// "If there are two ASCII digits available where a <COLOR> is allowed,
// then two characters MUST always be read for it and displayed as described below."
// we rely on greedy matching to implement this for both forms:
// (\x03)00,01
colorForeBackRe = regexp.MustCompile(`^([0-9]{1,2}),([0-9]{1,2})`)
// (\x03)00
colorForeRe = regexp.MustCompile(`^([0-9]{1,2})`)
)
// Split takes an IRC message (typically a PRIVMSG or NOTICE final parameter)
// containing IRC formatting control codes, and splits it into substrings with
// associated formatting information.
func Split(raw string) (result []FormattedSubstring) {
var chunk FormattedSubstring
for {
// skip to the next metacharacter, or the end of the string
if idx := strings.IndexAny(raw, metacharacters); idx != 0 {
if idx == -1 {
idx = len(raw)
}
chunk.Content = raw[:idx]
if len(chunk.Content) != 0 {
result = append(result, chunk)
}
raw = raw[idx:]
}
if len(raw) == 0 {
return
}
// we're at a metacharacter. by default, all previous formatting carries over
metacharacter := raw[0]
raw = raw[1:]
switch metacharacter {
case bold[0]:
chunk.Bold = !chunk.Bold
case monospace[0]:
chunk.Monospace = !chunk.Monospace
case strikethrough[0]:
chunk.Strikethrough = !chunk.Strikethrough
case underline[0]:
chunk.Underline = !chunk.Underline
case italic[0]:
chunk.Italic = !chunk.Italic
case reverseColour[0]:
chunk.ReverseColor = !chunk.ReverseColor
case reset[0]:
chunk = FormattedSubstring{}
case colour[0]:
// preferentially match the "\x0399,01" form, then "\x0399";
// if neither of those matches, then it's a reset
if matches := colorForeBackRe.FindStringSubmatch(raw); len(matches) != 0 {
chunk.ForegroundColor = ParseColor(matches[1])
chunk.BackgroundColor = ParseColor(matches[2])
raw = raw[len(matches[0]):]
} else if matches := colorForeRe.FindStringSubmatch(raw); len(matches) != 0 {
chunk.ForegroundColor = ParseColor(matches[1])
raw = raw[len(matches[0]):]
} else {
chunk.ForegroundColor = ColorCode{}
chunk.BackgroundColor = ColorCode{}
}
default:
// should be impossible, but just ignore it
}
}
}
var ( var (
// valtoescape replaces most of IRC characters with our escapes. // valtoescape replaces most of IRC characters with our escapes.
valtoescape = strings.NewReplacer("$", "$$", colour, "$c", reverseColour, "$v", bold, "$b", italic, "$i", strikethrough, "$s", underline, "$u", monospace, "$m", reset, "$r") valtoescape = strings.NewReplacer("$", "$$", colour, "$c", reverseColour, "$v", bold, "$b", italic, "$i", strikethrough, "$s", underline, "$u", monospace, "$m", reset, "$r")
// valToStrip replaces most of the IRC characters with nothing
valToStrip = strings.NewReplacer(colour, "$c", reverseColour, "", bold, "", italic, "", strikethrough, "", underline, "", monospace, "", reset, "")
// escapetoval contains most of our escapes and how they map to real IRC characters. // escapetoval contains most of our escapes and how they map to real IRC characters.
// intentionally skips colour, since that's handled elsewhere. // intentionally skips colour, since that's handled elsewhere.
@ -98,7 +201,9 @@ var (
"light blue": "12", "light blue": "12",
"pink": "13", "pink": "13",
"grey": "14", "grey": "14",
"gray": "14",
"light grey": "15", "light grey": "15",
"light gray": "15",
"default": "99", "default": "99",
} }
@ -123,7 +228,7 @@ func Escape(in string) string {
out.WriteString("$c") out.WriteString("$c")
inRunes = inRunes[2:] // strip colour code chars inRunes = inRunes[2:] // strip colour code chars
if len(inRunes) < 1 || !strings.Contains(colours1, string(inRunes[0])) { if len(inRunes) < 1 || !isDigit(inRunes[0]) {
out.WriteString("[]") out.WriteString("[]")
continue continue
} }
@ -131,14 +236,14 @@ func Escape(in string) string {
var foreBuffer, backBuffer string var foreBuffer, backBuffer string
foreBuffer += string(inRunes[0]) foreBuffer += string(inRunes[0])
inRunes = inRunes[1:] inRunes = inRunes[1:]
if 0 < len(inRunes) && strings.Contains(colours1, string(inRunes[0])) { if 0 < len(inRunes) && isDigit(inRunes[0]) {
foreBuffer += string(inRunes[0]) foreBuffer += string(inRunes[0])
inRunes = inRunes[1:] inRunes = inRunes[1:]
} }
if 1 < len(inRunes) && inRunes[0] == ',' && strings.Contains(colours1, string(inRunes[1])) { if 1 < len(inRunes) && inRunes[0] == ',' && isDigit(inRunes[1]) {
backBuffer += string(inRunes[1]) backBuffer += string(inRunes[1])
inRunes = inRunes[2:] inRunes = inRunes[2:]
if 0 < len(inRunes) && strings.Contains(colours1, string(inRunes[0])) { if 0 < len(inRunes) && isDigit(inRunes[1]) {
backBuffer += string(inRunes[0]) backBuffer += string(inRunes[0])
inRunes = inRunes[1:] inRunes = inRunes[1:]
} }
@ -178,52 +283,27 @@ func Escape(in string) string {
return out.String() return out.String()
} }
func isDigit(r rune) bool {
return '0' <= r && r <= '9' // don't use unicode.IsDigit, it includes non-ASCII numerals
}
// Strip takes a raw IRC string and removes it with all formatting codes removed // Strip takes a raw IRC string and removes it with all formatting codes removed
// IE, it turns this: "This is a \x02cool\x02, \x034red\x0f message!" // IE, it turns this: "This is a \x02cool\x02, \x034red\x0f message!"
// into: "This is a cool, red message!" // into: "This is a cool, red message!"
func Strip(in string) string { func Strip(in string) string {
out := strings.Builder{} splitChunks := Split(in)
runes := []rune(in) if len(splitChunks) == 0 {
if out.Len() < len(runes) { // Reduce allocations where needed return ""
out.Grow(len(in) - out.Len()) } else if len(splitChunks) == 1 {
} return splitChunks[0].Content
for len(runes) > 0 {
switch runes[0] {
case runebold, runemonospace, runereverseColour, runeitalic, runestrikethrough, runeunderline, runereset:
runes = runes[1:]
case runecolour:
runes = removeColour(runes)
default:
out.WriteRune(runes[0])
runes = runes[1:]
}
}
return out.String()
}
func removeNumber(runes []rune) []rune {
if len(runes) > 0 && runes[0] >= '0' && runes[0] <= '9' {
runes = runes[1:]
}
return runes
}
func removeColour(runes []rune) []rune {
if runes[0] != runecolour {
return runes
}
runes = runes[1:]
runes = removeNumber(runes)
runes = removeNumber(runes)
if len(runes) > 1 && runes[0] == ',' && runes[1] >= '0' && runes[1] <= '9' {
runes = runes[2:]
} else { } else {
return runes // Nothing else because we dont have a comma var buf strings.Builder
buf.Grow(len(in))
for _, chunk := range splitChunks {
buf.WriteString(chunk.Content)
}
return buf.String()
} }
runes = removeNumber(runes)
return runes
} }
// resolve "light blue" to "12", "12" to "12", "asdf" to "", etc. // resolve "light blue" to "12", "12" to "12", "asdf" to "", etc.

View File

@ -238,7 +238,7 @@ func parseLine(line string, maxTagDataLength int, truncateLen int) (ircmsg Messa
// truncate if desired // truncate if desired
if truncateLen != 0 && truncateLen < len(line) { if truncateLen != 0 && truncateLen < len(line) {
err = ErrorBodyTooLong err = ErrorBodyTooLong
line = line[:truncateLen] line = TruncateUTF8Safe(line, truncateLen)
} }
// modern: "These message parts, and parameters themselves, are separated // modern: "These message parts, and parameters themselves, are separated

29
vendor/github.com/ergochat/irc-go/ircmsg/unicode.go generated vendored Normal file
View File

@ -0,0 +1,29 @@
// Copyright (c) 2021 Shivaram Lingamneni
// Released under the MIT License
package ircmsg
import (
"unicode/utf8"
)
// TruncateUTF8Safe truncates a message, respecting UTF8 boundaries. If a message
// was originally valid UTF8, TruncateUTF8Safe will not make it invalid; instead
// it will truncate additional bytes as needed, back to the last valid
// UTF8-encoded codepoint. If a message is not UTF8, TruncateUTF8Safe will truncate
// at most 3 additional bytes before giving up.
func TruncateUTF8Safe(message string, byteLimit int) (result string) {
if len(message) <= byteLimit {
return message
}
message = message[:byteLimit]
for i := 0; i < (utf8.UTFMax - 1); i++ {
r, n := utf8.DecodeLastRuneInString(message)
if r == utf8.RuneError && n <= 1 {
message = message[:len(message)-1]
} else {
break
}
}
return message
}

View File

@ -7,24 +7,11 @@ import (
"strings" "strings"
"unicode" "unicode"
"unicode/utf8" "unicode/utf8"
"github.com/ergochat/irc-go/ircmsg"
) )
// truncate a message, taking care not to make valid UTF8 into invalid UTF8 var TruncateUTF8Safe = ircmsg.TruncateUTF8Safe
func TruncateUTF8Safe(message string, byteLimit int) (result string) {
if len(message) <= byteLimit {
return message
}
message = message[:byteLimit]
for i := 0; i < (utf8.UTFMax - 1); i++ {
r, n := utf8.DecodeLastRuneInString(message)
if r == utf8.RuneError && n <= 1 {
message = message[:len(message)-1]
} else {
break
}
}
return message
}
// Sanitizes human-readable text to make it safe for IRC; // Sanitizes human-readable text to make it safe for IRC;
// assumes UTF-8 and uses the replacement character where // assumes UTF-8 and uses the replacement character where

2
vendor/modules.txt vendored
View File

@ -16,7 +16,7 @@ github.com/ergochat/confusables
# github.com/ergochat/go-ident v0.0.0-20200511222032-830550b1d775 # github.com/ergochat/go-ident v0.0.0-20200511222032-830550b1d775
## explicit ## explicit
github.com/ergochat/go-ident github.com/ergochat/go-ident
# github.com/ergochat/irc-go v0.2.0 # github.com/ergochat/irc-go v0.4.0
## explicit; go 1.15 ## explicit; go 1.15
github.com/ergochat/irc-go/ircfmt github.com/ergochat/irc-go/ircfmt
github.com/ergochat/irc-go/ircmsg github.com/ergochat/irc-go/ircmsg