mirror of
https://github.com/ergochat/ergo.git
synced 2024-12-22 18:52:41 +01:00
Merge pull request #752 from slingamn/multiline.1
remove oragono.io/maxline-2 and fmsgid
This commit is contained in:
commit
fbfa32bf4c
@ -81,12 +81,6 @@ CAPDEFS = [
|
|||||||
url="https://gist.github.com/DanielOaks/8126122f74b26012a3de37db80e4e0c6",
|
url="https://gist.github.com/DanielOaks/8126122f74b26012a3de37db80e4e0c6",
|
||||||
standard="proposed IRCv3",
|
standard="proposed IRCv3",
|
||||||
),
|
),
|
||||||
CapDef(
|
|
||||||
identifier="MaxLine",
|
|
||||||
name="oragono.io/maxline-2",
|
|
||||||
url="https://oragono.io/maxline-2",
|
|
||||||
standard="Oragono-specific",
|
|
||||||
),
|
|
||||||
CapDef(
|
CapDef(
|
||||||
identifier="MessageTags",
|
identifier="MessageTags",
|
||||||
name="message-tags",
|
name="message-tags",
|
||||||
|
@ -58,7 +58,6 @@ const (
|
|||||||
// More draft names associated with draft/multiline:
|
// More draft names associated with draft/multiline:
|
||||||
MultilineBatchType = "draft/multiline"
|
MultilineBatchType = "draft/multiline"
|
||||||
MultilineConcatTag = "draft/multiline-concat"
|
MultilineConcatTag = "draft/multiline-concat"
|
||||||
MultilineFmsgidTag = "draft/fmsgid"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -7,7 +7,7 @@ package caps
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
// number of recognized capabilities:
|
// number of recognized capabilities:
|
||||||
numCapabs = 27
|
numCapabs = 26
|
||||||
// length of the uint64 array that represents the bitset:
|
// length of the uint64 array that represents the bitset:
|
||||||
bitsetLen = 1
|
bitsetLen = 1
|
||||||
)
|
)
|
||||||
@ -89,10 +89,6 @@ const (
|
|||||||
// https://oragono.io/bnc
|
// https://oragono.io/bnc
|
||||||
Bouncer Capability = iota
|
Bouncer Capability = iota
|
||||||
|
|
||||||
// MaxLine is the Oragono-specific capability named "oragono.io/maxline-2":
|
|
||||||
// https://oragono.io/maxline-2
|
|
||||||
MaxLine Capability = iota
|
|
||||||
|
|
||||||
// Nope is the Oragono vendor capability named "oragono.io/nope":
|
// Nope is the Oragono vendor capability named "oragono.io/nope":
|
||||||
// https://oragono.io/nope
|
// https://oragono.io/nope
|
||||||
Nope Capability = iota
|
Nope Capability = iota
|
||||||
@ -144,7 +140,6 @@ var (
|
|||||||
"message-tags",
|
"message-tags",
|
||||||
"multi-prefix",
|
"multi-prefix",
|
||||||
"oragono.io/bnc",
|
"oragono.io/bnc",
|
||||||
"oragono.io/maxline-2",
|
|
||||||
"oragono.io/nope",
|
"oragono.io/nope",
|
||||||
"sasl",
|
"sasl",
|
||||||
"server-time",
|
"server-time",
|
||||||
|
@ -643,7 +643,7 @@ func (channel *Channel) Join(client *Client, key string, isSajoin bool, rb *Resp
|
|||||||
|
|
||||||
channel.regenerateMembersCache()
|
channel.regenerateMembersCache()
|
||||||
|
|
||||||
message = utils.MakeSplitMessage("", true)
|
message = utils.MakeMessage("")
|
||||||
histItem := history.Item{
|
histItem := history.Item{
|
||||||
Type: history.Join,
|
Type: history.Join,
|
||||||
Nick: details.nickMask,
|
Nick: details.nickMask,
|
||||||
@ -766,7 +766,7 @@ func (channel *Channel) Part(client *Client, message string, rb *ResponseBuffer)
|
|||||||
|
|
||||||
channel.Quit(client)
|
channel.Quit(client)
|
||||||
|
|
||||||
splitMessage := utils.MakeSplitMessage(message, true)
|
splitMessage := utils.MakeMessage(message)
|
||||||
|
|
||||||
details := client.Details()
|
details := client.Details()
|
||||||
params := make([]string, 1, 2)
|
params := make([]string, 1, 2)
|
||||||
@ -1233,7 +1233,7 @@ func (channel *Channel) Kick(client *Client, target *Client, comment string, rb
|
|||||||
comment = comment[:kicklimit]
|
comment = comment[:kicklimit]
|
||||||
}
|
}
|
||||||
|
|
||||||
message := utils.MakeSplitMessage(comment, true)
|
message := utils.MakeMessage(comment)
|
||||||
clientMask := client.NickMaskString()
|
clientMask := client.NickMaskString()
|
||||||
clientAccount := client.AccountName()
|
clientAccount := client.AccountName()
|
||||||
|
|
||||||
|
@ -112,7 +112,6 @@ type Session struct {
|
|||||||
quitMessage string
|
quitMessage string
|
||||||
|
|
||||||
capabilities caps.Set
|
capabilities caps.Set
|
||||||
maxlenRest uint32
|
|
||||||
capState caps.State
|
capState caps.State
|
||||||
capVersion caps.Version
|
capVersion caps.Version
|
||||||
|
|
||||||
@ -148,21 +147,6 @@ func (sd *Session) SetQuitMessage(message string) (set bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// set the negotiated message length based on session capabilities
|
|
||||||
func (session *Session) SetMaxlenRest() {
|
|
||||||
maxlenRest := 512
|
|
||||||
if session.capabilities.Has(caps.MaxLine) {
|
|
||||||
maxlenRest = session.client.server.Config().Limits.LineLen.Rest
|
|
||||||
}
|
|
||||||
atomic.StoreUint32(&session.maxlenRest, uint32(maxlenRest))
|
|
||||||
}
|
|
||||||
|
|
||||||
// allow the negotiated message length limit to be read without locks; this is a convenience
|
|
||||||
// so that Session.SendRawMessage doesn't have to acquire any Client locks
|
|
||||||
func (session *Session) MaxlenRest() int {
|
|
||||||
return int(atomic.LoadUint32(&session.maxlenRest))
|
|
||||||
}
|
|
||||||
|
|
||||||
// returns whether the session was actively destroyed (for example, by ping
|
// returns whether the session was actively destroyed (for example, by ping
|
||||||
// timeout or NS GHOST).
|
// timeout or NS GHOST).
|
||||||
// avoids a race condition between asynchronous idle-timing-out of sessions,
|
// avoids a race condition between asynchronous idle-timing-out of sessions,
|
||||||
@ -240,9 +224,8 @@ func (server *Server) RunClient(conn clientConn, proxyLine string) {
|
|||||||
|
|
||||||
now := time.Now().UTC()
|
now := time.Now().UTC()
|
||||||
config := server.Config()
|
config := server.Config()
|
||||||
fullLineLenLimit := ircmsg.MaxlenTagsFromClient + config.Limits.LineLen.Rest
|
|
||||||
// give them 1k of grace over the limit:
|
// give them 1k of grace over the limit:
|
||||||
socket := NewSocket(conn.Conn, fullLineLenLimit+1024, config.Server.MaxSendQBytes)
|
socket := NewSocket(conn.Conn, ircmsg.MaxlenTagsFromClient+512+1024, config.Server.MaxSendQBytes)
|
||||||
client := &Client{
|
client := &Client{
|
||||||
atime: now,
|
atime: now,
|
||||||
channels: make(ChannelSet),
|
channels: make(ChannelSet),
|
||||||
@ -271,7 +254,6 @@ func (server *Server) RunClient(conn clientConn, proxyLine string) {
|
|||||||
atime: now,
|
atime: now,
|
||||||
realIP: realIP,
|
realIP: realIP,
|
||||||
}
|
}
|
||||||
session.SetMaxlenRest()
|
|
||||||
client.sessions = []*Session{session}
|
client.sessions = []*Session{session}
|
||||||
|
|
||||||
if conn.Config.TLSConfig != nil {
|
if conn.Config.TLSConfig != nil {
|
||||||
@ -493,8 +475,6 @@ func (client *Client) run(session *Session, proxyLine string) {
|
|||||||
firstLine := !isReattach
|
firstLine := !isReattach
|
||||||
|
|
||||||
for {
|
for {
|
||||||
maxlenRest := session.MaxlenRest()
|
|
||||||
|
|
||||||
var line string
|
var line string
|
||||||
var err error
|
var err error
|
||||||
if proxyLine == "" {
|
if proxyLine == "" {
|
||||||
@ -545,7 +525,7 @@ func (client *Client) run(session *Session, proxyLine string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
msg, err := ircmsg.ParseLineStrict(line, true, maxlenRest)
|
msg, err := ircmsg.ParseLineStrict(line, true, 512)
|
||||||
if err == ircmsg.ErrorLineIsEmpty {
|
if err == ircmsg.ErrorLineIsEmpty {
|
||||||
continue
|
continue
|
||||||
} else if err == ircmsg.ErrorLineTooLong {
|
} else if err == ircmsg.ErrorLineTooLong {
|
||||||
@ -1161,7 +1141,7 @@ func (client *Client) destroy(session *Session) {
|
|||||||
// clean up monitor state
|
// clean up monitor state
|
||||||
client.server.monitorManager.RemoveAll(client)
|
client.server.monitorManager.RemoveAll(client)
|
||||||
|
|
||||||
splitQuitMessage := utils.MakeSplitMessage(quitMessage, true)
|
splitQuitMessage := utils.MakeMessage(quitMessage)
|
||||||
// clean up channels
|
// clean up channels
|
||||||
// (note that if this is a reattach, client has no channels and therefore no friends)
|
// (note that if this is a reattach, client has no channels and therefore no friends)
|
||||||
friends := make(ClientSet)
|
friends := make(ClientSet)
|
||||||
@ -1216,7 +1196,8 @@ func (client *Client) destroy(session *Session) {
|
|||||||
// SendSplitMsgFromClient sends an IRC PRIVMSG/NOTICE coming from a specific client.
|
// SendSplitMsgFromClient sends an IRC PRIVMSG/NOTICE coming from a specific client.
|
||||||
// Adds account-tag to the line as well.
|
// Adds account-tag to the line as well.
|
||||||
func (session *Session) sendSplitMsgFromClientInternal(blocking bool, nickmask, accountName string, tags map[string]string, command, target string, message utils.SplitMessage) {
|
func (session *Session) sendSplitMsgFromClientInternal(blocking bool, nickmask, accountName string, tags map[string]string, command, target string, message utils.SplitMessage) {
|
||||||
if message.Is512() || session.capabilities.Has(caps.MaxLine) {
|
// TODO no maxline support
|
||||||
|
if message.Is512() {
|
||||||
session.sendFromClientInternal(blocking, message.Time, message.Msgid, nickmask, accountName, tags, command, target, message.Message)
|
session.sendFromClientInternal(blocking, message.Time, message.Msgid, nickmask, accountName, tags, command, target, message.Message)
|
||||||
} else {
|
} else {
|
||||||
if message.IsMultiline() && session.capabilities.Has(caps.Multiline) {
|
if message.IsMultiline() && session.capabilities.Has(caps.Multiline) {
|
||||||
@ -1224,8 +1205,12 @@ func (session *Session) sendSplitMsgFromClientInternal(blocking bool, nickmask,
|
|||||||
session.SendRawMessage(msg, blocking)
|
session.SendRawMessage(msg, blocking)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for _, messagePair := range message.Wrapped {
|
for i, messagePair := range message.Split {
|
||||||
session.sendFromClientInternal(blocking, message.Time, messagePair.Msgid, nickmask, accountName, tags, command, target, messagePair.Message)
|
var msgid string
|
||||||
|
if i == 0 {
|
||||||
|
msgid = message.Msgid
|
||||||
|
}
|
||||||
|
session.sendFromClientInternal(blocking, message.Time, msgid, nickmask, accountName, tags, command, target, messagePair.Message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1268,10 +1253,9 @@ func (session *Session) composeMultilineBatch(fromNickMask, fromAccount string,
|
|||||||
}
|
}
|
||||||
result = append(result, batchStart)
|
result = append(result, batchStart)
|
||||||
|
|
||||||
for _, msg := range message.Wrapped {
|
for _, msg := range message.Split {
|
||||||
message := ircmsg.MakeMessage(nil, fromNickMask, command, target, msg.Message)
|
message := ircmsg.MakeMessage(nil, fromNickMask, command, target, msg.Message)
|
||||||
message.SetTag("batch", batchID)
|
message.SetTag("batch", batchID)
|
||||||
message.SetTag(caps.MultilineFmsgidTag, msg.Msgid)
|
|
||||||
if msg.Concat {
|
if msg.Concat {
|
||||||
message.SetTag(caps.MultilineConcatTag, "")
|
message.SetTag(caps.MultilineConcatTag, "")
|
||||||
}
|
}
|
||||||
@ -1310,8 +1294,7 @@ func (session *Session) SendRawMessage(message ircmsg.IrcMessage, blocking bool)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// assemble message
|
// assemble message
|
||||||
maxlenRest := session.MaxlenRest()
|
line, err := message.LineBytesStrict(false, 512)
|
||||||
line, err := message.LineBytesStrict(false, maxlenRest)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logline := fmt.Sprintf("Error assembling message for sending: %v\n%s", err, debug.Stack())
|
logline := fmt.Sprintf("Error assembling message for sending: %v\n%s", err, debug.Stack())
|
||||||
session.client.server.logger.Error("internal", logline)
|
session.client.server.logger.Error("internal", logline)
|
||||||
|
@ -237,24 +237,18 @@ type OperConfig struct {
|
|||||||
Modes string
|
Modes string
|
||||||
}
|
}
|
||||||
|
|
||||||
// LineLenConfig controls line lengths.
|
|
||||||
type LineLenLimits struct {
|
|
||||||
Rest int
|
|
||||||
}
|
|
||||||
|
|
||||||
// Various server-enforced limits on data size.
|
// Various server-enforced limits on data size.
|
||||||
type Limits struct {
|
type Limits struct {
|
||||||
AwayLen int `yaml:"awaylen"`
|
AwayLen int `yaml:"awaylen"`
|
||||||
ChanListModes int `yaml:"chan-list-modes"`
|
ChanListModes int `yaml:"chan-list-modes"`
|
||||||
ChannelLen int `yaml:"channellen"`
|
ChannelLen int `yaml:"channellen"`
|
||||||
IdentLen int `yaml:"identlen"`
|
IdentLen int `yaml:"identlen"`
|
||||||
KickLen int `yaml:"kicklen"`
|
KickLen int `yaml:"kicklen"`
|
||||||
LineLen LineLenLimits `yaml:"linelen"`
|
MonitorEntries int `yaml:"monitor-entries"`
|
||||||
MonitorEntries int `yaml:"monitor-entries"`
|
NickLen int `yaml:"nicklen"`
|
||||||
NickLen int `yaml:"nicklen"`
|
TopicLen int `yaml:"topiclen"`
|
||||||
TopicLen int `yaml:"topiclen"`
|
WhowasEntries int `yaml:"whowas-entries"`
|
||||||
WhowasEntries int `yaml:"whowas-entries"`
|
RegistrationMessages int `yaml:"registration-messages"`
|
||||||
RegistrationMessages int `yaml:"registration-messages"`
|
|
||||||
Multiline struct {
|
Multiline struct {
|
||||||
MaxBytes int `yaml:"max-bytes"`
|
MaxBytes int `yaml:"max-bytes"`
|
||||||
MaxLines int `yaml:"max-lines"`
|
MaxLines int `yaml:"max-lines"`
|
||||||
@ -671,7 +665,9 @@ func LoadConfig(filename string) (config *Config, err error) {
|
|||||||
return nil, fmt.Errorf("STS port is incorrect, should be 0 if disabled: %d", config.Server.STS.Port)
|
return nil, fmt.Errorf("STS port is incorrect, should be 0 if disabled: %d", config.Server.STS.Port)
|
||||||
}
|
}
|
||||||
if config.Server.STS.STSOnlyBanner != "" {
|
if config.Server.STS.STSOnlyBanner != "" {
|
||||||
config.Server.STS.bannerLines = utils.WordWrap(config.Server.STS.STSOnlyBanner, 400)
|
for _, line := range strings.Split(config.Server.STS.STSOnlyBanner, "\n") {
|
||||||
|
config.Server.STS.bannerLines = append(config.Server.STS.bannerLines, strings.TrimSpace(line))
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
config.Server.STS.bannerLines = []string{fmt.Sprintf("This server is only accessible over TLS. Please reconnect using TLS on port %d.", config.Server.STS.Port)}
|
config.Server.STS.bannerLines = []string{fmt.Sprintf("This server is only accessible over TLS. Please reconnect using TLS on port %d.", config.Server.STS.Port)}
|
||||||
}
|
}
|
||||||
@ -705,16 +701,6 @@ func LoadConfig(filename string) (config *Config, err error) {
|
|||||||
}
|
}
|
||||||
config.Server.WebIRC = newWebIRC
|
config.Server.WebIRC = newWebIRC
|
||||||
|
|
||||||
// process limits
|
|
||||||
if config.Limits.LineLen.Rest < 512 {
|
|
||||||
config.Limits.LineLen.Rest = 512
|
|
||||||
}
|
|
||||||
if config.Limits.LineLen.Rest == 512 {
|
|
||||||
config.Server.supportedCaps.Disable(caps.MaxLine)
|
|
||||||
} else {
|
|
||||||
config.Server.capValues[caps.MaxLine] = strconv.Itoa(config.Limits.LineLen.Rest)
|
|
||||||
}
|
|
||||||
|
|
||||||
if config.Limits.Multiline.MaxBytes <= 0 {
|
if config.Limits.Multiline.MaxBytes <= 0 {
|
||||||
config.Server.supportedCaps.Disable(caps.Multiline)
|
config.Server.supportedCaps.Disable(caps.Multiline)
|
||||||
} else {
|
} else {
|
||||||
|
@ -352,8 +352,6 @@ func batchHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Res
|
|||||||
} else {
|
} else {
|
||||||
rb.session.batch.target = msg.Params[2]
|
rb.session.batch.target = msg.Params[2]
|
||||||
// save the response label for later
|
// save the response label for later
|
||||||
// XXX changing the label inside a handler is a bit dodgy, but it works here
|
|
||||||
// because there's no way we could have triggered a flush up to this point
|
|
||||||
rb.session.batch.responseLabel = rb.Label
|
rb.session.batch.responseLabel = rb.Label
|
||||||
rb.Label = ""
|
rb.Label = ""
|
||||||
}
|
}
|
||||||
@ -366,13 +364,15 @@ func batchHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Res
|
|||||||
} else {
|
} else {
|
||||||
batch := rb.session.batch
|
batch := rb.session.batch
|
||||||
rb.session.batch = MultilineBatch{}
|
rb.session.batch = MultilineBatch{}
|
||||||
batch.message.Time = time.Now().UTC()
|
// time tag should correspond to the time when the message was completed
|
||||||
|
batch.message.SetTime()
|
||||||
histType, err := msgCommandToHistType(batch.command)
|
histType, err := msgCommandToHistType(batch.command)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
histType = history.Privmsg
|
histType = history.Privmsg
|
||||||
batch.command = "PRIVMSG"
|
batch.command = "PRIVMSG"
|
||||||
}
|
}
|
||||||
// see previous caution about modifying ResponseBuffer.Label
|
// XXX changing the label inside a handler is a bit dodgy, but it works here
|
||||||
|
// because there's no way we could have triggered a flush up to this point
|
||||||
rb.Label = batch.responseLabel
|
rb.Label = batch.responseLabel
|
||||||
dispatchMessageToTarget(client, batch.tags, histType, batch.command, batch.target, batch.message, rb)
|
dispatchMessageToTarget(client, batch.tags, histType, batch.command, batch.target, batch.message, rb)
|
||||||
}
|
}
|
||||||
@ -515,10 +515,6 @@ func capHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Respo
|
|||||||
rb.session.SetResumeID(id)
|
rb.session.SetResumeID(id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// update maxlenrest, just in case they altered the maxline cap
|
|
||||||
rb.session.SetMaxlenRest()
|
|
||||||
|
|
||||||
case "END":
|
case "END":
|
||||||
if !client.registered {
|
if !client.registered {
|
||||||
rb.session.capState = caps.NegotiatedState
|
rb.session.capState = caps.NegotiatedState
|
||||||
@ -1962,7 +1958,7 @@ func messageHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *R
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
// each target gets distinct msgids
|
// each target gets distinct msgids
|
||||||
splitMsg := utils.MakeSplitMessage(message, !rb.session.capabilities.Has(caps.MaxLine))
|
splitMsg := utils.MakeMessage(message)
|
||||||
dispatchMessageToTarget(client, clientOnlyTags, histType, msg.Command, targetString, splitMsg, rb)
|
dispatchMessageToTarget(client, clientOnlyTags, histType, msg.Command, targetString, splitMsg, rb)
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
@ -50,15 +50,7 @@ type Item struct {
|
|||||||
|
|
||||||
// HasMsgid tests whether a message has the message id `msgid`.
|
// HasMsgid tests whether a message has the message id `msgid`.
|
||||||
func (item *Item) HasMsgid(msgid string) bool {
|
func (item *Item) HasMsgid(msgid string) bool {
|
||||||
if item.Message.Msgid == msgid {
|
return item.Message.Msgid == msgid
|
||||||
return true
|
|
||||||
}
|
|
||||||
for _, pair := range item.Message.Wrapped {
|
|
||||||
if pair.Msgid == msgid {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (item *Item) isStorable() bool {
|
func (item *Item) isStorable() bool {
|
||||||
|
@ -57,7 +57,7 @@ func performNickChange(server *Server, client *Client, target *Client, session *
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
message := utils.MakeSplitMessage("", true)
|
message := utils.MakeMessage("")
|
||||||
histItem := history.Item{
|
histItem := history.Item{
|
||||||
Type: history.Nick,
|
Type: history.Nick,
|
||||||
Nick: origNickMask,
|
Nick: origNickMask,
|
||||||
|
@ -118,7 +118,7 @@ func (rb *ResponseBuffer) AddFromClient(time time.Time, msgid string, fromNickMa
|
|||||||
|
|
||||||
// AddSplitMessageFromClient adds a new split message from a specific client to our queue.
|
// AddSplitMessageFromClient adds a new split message from a specific client to our queue.
|
||||||
func (rb *ResponseBuffer) AddSplitMessageFromClient(fromNickMask string, fromAccount string, tags map[string]string, command string, target string, message utils.SplitMessage) {
|
func (rb *ResponseBuffer) AddSplitMessageFromClient(fromNickMask string, fromAccount string, tags map[string]string, command string, target string, message utils.SplitMessage) {
|
||||||
if message.Is512() || rb.session.capabilities.Has(caps.MaxLine) {
|
if message.Is512() {
|
||||||
rb.AddFromClient(message.Time, message.Msgid, fromNickMask, fromAccount, tags, command, target, message.Message)
|
rb.AddFromClient(message.Time, message.Msgid, fromNickMask, fromAccount, tags, command, target, message.Message)
|
||||||
} else {
|
} else {
|
||||||
if message.IsMultiline() && rb.session.capabilities.Has(caps.Multiline) {
|
if message.IsMultiline() && rb.session.capabilities.Has(caps.Multiline) {
|
||||||
@ -127,8 +127,12 @@ func (rb *ResponseBuffer) AddSplitMessageFromClient(fromNickMask string, fromAcc
|
|||||||
rb.setNestedBatchTag(&batch[len(batch)-1])
|
rb.setNestedBatchTag(&batch[len(batch)-1])
|
||||||
rb.messages = append(rb.messages, batch...)
|
rb.messages = append(rb.messages, batch...)
|
||||||
} else {
|
} else {
|
||||||
for _, messagePair := range message.Wrapped {
|
for i, messagePair := range message.Split {
|
||||||
rb.AddFromClient(message.Time, messagePair.Msgid, fromNickMask, fromAccount, tags, command, target, messagePair.Message)
|
var msgid string
|
||||||
|
if i == 0 {
|
||||||
|
msgid = message.Msgid
|
||||||
|
}
|
||||||
|
rb.AddFromClient(message.Time, msgid, fromNickMask, fromAccount, tags, command, target, messagePair.Message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -564,10 +564,7 @@ func (server *Server) applyConfig(config *Config, initial bool) (err error) {
|
|||||||
globalCasemappingSetting = config.Server.Casemapping
|
globalCasemappingSetting = config.Server.Casemapping
|
||||||
} else {
|
} else {
|
||||||
// enforce configs that can't be changed after launch:
|
// enforce configs that can't be changed after launch:
|
||||||
currentLimits := server.Config().Limits
|
if server.name != config.Server.Name {
|
||||||
if currentLimits.LineLen.Rest != config.Limits.LineLen.Rest {
|
|
||||||
return fmt.Errorf("Maximum line length (linelen) cannot be changed after launching the server, rehash aborted")
|
|
||||||
} else if server.name != config.Server.Name {
|
|
||||||
return fmt.Errorf("Server name cannot be changed after launching the server, rehash aborted")
|
return fmt.Errorf("Server name cannot be changed after launching the server, rehash aborted")
|
||||||
} else if server.Config().Datastore.Path != config.Datastore.Path {
|
} else if server.Config().Datastore.Path != config.Datastore.Path {
|
||||||
return fmt.Errorf("Datastore path cannot be changed after launching the server, rehash aborted")
|
return fmt.Errorf("Datastore path cannot be changed after launching the server, rehash aborted")
|
||||||
|
@ -15,89 +15,29 @@ func IsRestrictedCTCPMessage(message string) bool {
|
|||||||
return strings.HasPrefix(message, "\x01") && !strings.HasPrefix(message, "\x01ACTION")
|
return strings.HasPrefix(message, "\x01") && !strings.HasPrefix(message, "\x01ACTION")
|
||||||
}
|
}
|
||||||
|
|
||||||
// WordWrap wraps the given text into a series of lines that don't exceed lineWidth characters.
|
|
||||||
func WordWrap(text string, lineWidth int) []string {
|
|
||||||
var lines []string
|
|
||||||
var cacheLine, cacheWord bytes.Buffer
|
|
||||||
|
|
||||||
for _, char := range text {
|
|
||||||
if char == '\r' {
|
|
||||||
continue
|
|
||||||
} else if char == '\n' {
|
|
||||||
cacheLine.Write(cacheWord.Bytes())
|
|
||||||
lines = append(lines, cacheLine.String())
|
|
||||||
cacheWord.Reset()
|
|
||||||
cacheLine.Reset()
|
|
||||||
} else if (char == ' ' || char == '-') && cacheLine.Len()+cacheWord.Len()+1 < lineWidth {
|
|
||||||
// natural word boundary
|
|
||||||
cacheLine.Write(cacheWord.Bytes())
|
|
||||||
cacheLine.WriteRune(char)
|
|
||||||
cacheWord.Reset()
|
|
||||||
} else if lineWidth <= cacheLine.Len()+cacheWord.Len()+1 {
|
|
||||||
// time to wrap to next line
|
|
||||||
if cacheLine.Len() < (lineWidth / 2) {
|
|
||||||
// this word takes up more than half a line... just split in the middle of the word
|
|
||||||
cacheLine.Write(cacheWord.Bytes())
|
|
||||||
cacheLine.WriteRune(char)
|
|
||||||
cacheWord.Reset()
|
|
||||||
} else {
|
|
||||||
cacheWord.WriteRune(char)
|
|
||||||
}
|
|
||||||
lines = append(lines, cacheLine.String())
|
|
||||||
cacheLine.Reset()
|
|
||||||
} else {
|
|
||||||
// normal character
|
|
||||||
cacheWord.WriteRune(char)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if 0 < cacheWord.Len() {
|
|
||||||
cacheLine.Write(cacheWord.Bytes())
|
|
||||||
}
|
|
||||||
if 0 < cacheLine.Len() {
|
|
||||||
lines = append(lines, cacheLine.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
return lines
|
|
||||||
}
|
|
||||||
|
|
||||||
type MessagePair struct {
|
type MessagePair struct {
|
||||||
Message string
|
Message string
|
||||||
Msgid string
|
|
||||||
Concat bool // should be relayed with the multiline-concat tag
|
Concat bool // should be relayed with the multiline-concat tag
|
||||||
}
|
}
|
||||||
|
|
||||||
// SplitMessage represents a message that's been split for sending.
|
// SplitMessage represents a message that's been split for sending.
|
||||||
// Three possibilities:
|
// Two possibilities:
|
||||||
// (a) Standard message that can be relayed on a single 512-byte line
|
// (a) Standard message that can be relayed on a single 512-byte line
|
||||||
// (MessagePair contains the message, Wrapped == nil)
|
// (MessagePair contains the message, Wrapped == nil)
|
||||||
// (b) oragono.io/maxline-2 message that was split on the server side
|
// (b) multiline message that was split on the client side
|
||||||
// (MessagePair contains the unsplit message, Wrapped contains the split lines)
|
// (Message == "", Wrapped contains the split lines)
|
||||||
// (c) multiline message that was split on the client side
|
|
||||||
// (MessagePair is zero, Wrapped contains the split lines)
|
|
||||||
type SplitMessage struct {
|
type SplitMessage struct {
|
||||||
MessagePair
|
Message string
|
||||||
Wrapped []MessagePair // if this is nil, `Message` didn't need wrapping and can be sent to anyone
|
Msgid string
|
||||||
|
Split []MessagePair
|
||||||
Time time.Time
|
Time time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultLineWidth = 400
|
func MakeMessage(original string) (result SplitMessage) {
|
||||||
|
|
||||||
func MakeSplitMessage(original string, origIs512 bool) (result SplitMessage) {
|
|
||||||
result.Message = original
|
result.Message = original
|
||||||
result.Msgid = GenerateSecretToken()
|
result.Msgid = GenerateSecretToken()
|
||||||
result.Time = time.Now().UTC()
|
result.Time = time.Now().UTC()
|
||||||
|
|
||||||
if !origIs512 && defaultLineWidth < len(original) {
|
|
||||||
wrapped := WordWrap(original, defaultLineWidth)
|
|
||||||
result.Wrapped = make([]MessagePair, len(wrapped))
|
|
||||||
for i, wrappedMessage := range wrapped {
|
|
||||||
result.Wrapped[i] = MessagePair{
|
|
||||||
Message: wrappedMessage,
|
|
||||||
Msgid: GenerateSecretToken(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,30 +45,33 @@ func (sm *SplitMessage) Append(message string, concat bool) {
|
|||||||
if sm.Msgid == "" {
|
if sm.Msgid == "" {
|
||||||
sm.Msgid = GenerateSecretToken()
|
sm.Msgid = GenerateSecretToken()
|
||||||
}
|
}
|
||||||
sm.Wrapped = append(sm.Wrapped, MessagePair{
|
sm.Split = append(sm.Split, MessagePair{
|
||||||
Message: message,
|
Message: message,
|
||||||
Msgid: GenerateSecretToken(),
|
|
||||||
Concat: concat,
|
Concat: concat,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (sm *SplitMessage) SetTime() {
|
||||||
|
sm.Time = time.Now().UTC()
|
||||||
|
}
|
||||||
|
|
||||||
func (sm *SplitMessage) LenLines() int {
|
func (sm *SplitMessage) LenLines() int {
|
||||||
if sm.Wrapped == nil {
|
if sm.Split == nil {
|
||||||
if (sm.MessagePair == MessagePair{}) {
|
if sm.Message == "" {
|
||||||
return 0
|
return 0
|
||||||
} else {
|
} else {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return len(sm.Wrapped)
|
return len(sm.Split)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sm *SplitMessage) LenBytes() (result int) {
|
func (sm *SplitMessage) LenBytes() (result int) {
|
||||||
if sm.Wrapped == nil {
|
if sm.Split == nil {
|
||||||
return len(sm.Message)
|
return len(sm.Message)
|
||||||
}
|
}
|
||||||
for i := 0; i < len(sm.Wrapped); i++ {
|
for i := 0; i < len(sm.Split); i++ {
|
||||||
result += len(sm.Wrapped[i].Message)
|
result += len(sm.Split[i].Message)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -137,8 +80,8 @@ func (sm *SplitMessage) IsRestrictedCTCPMessage() bool {
|
|||||||
if IsRestrictedCTCPMessage(sm.Message) {
|
if IsRestrictedCTCPMessage(sm.Message) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
for i := 0; i < len(sm.Wrapped); i++ {
|
for i := 0; i < len(sm.Split); i++ {
|
||||||
if IsRestrictedCTCPMessage(sm.Wrapped[i].Message) {
|
if IsRestrictedCTCPMessage(sm.Split[i].Message) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -146,11 +89,11 @@ func (sm *SplitMessage) IsRestrictedCTCPMessage() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (sm *SplitMessage) IsMultiline() bool {
|
func (sm *SplitMessage) IsMultiline() bool {
|
||||||
return sm.Message == "" && len(sm.Wrapped) != 0
|
return sm.Message == "" && len(sm.Split) != 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sm *SplitMessage) Is512() bool {
|
func (sm *SplitMessage) Is512() bool {
|
||||||
return sm.Message != "" && sm.Wrapped == nil
|
return sm.Message != ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// TokenLineBuilder is a helper for building IRC lines composed of delimited tokens,
|
// TokenLineBuilder is a helper for building IRC lines composed of delimited tokens,
|
||||||
|
@ -4,61 +4,14 @@
|
|||||||
package utils
|
package utils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"reflect"
|
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
threeMusketeers = "In the meantime D’Artagnan, who had plunged into a bypath, continued his route and reached St. Cloud; but instead of following the main street he turned behind the château, reached a sort of retired lane, and found himself soon in front of the pavilion named. It was situated in a very private spot. A high wall, at the angle of which was the pavilion, ran along one side of this lane, and on the other was a little garden connected with a poor cottage which was protected by a hedge from passers-by."
|
|
||||||
|
|
||||||
monteCristo = `Both the count and Baptistin had told the truth when they announced to Morcerf the proposed visit of the major, which had served Monte Cristo as a pretext for declining Albert's invitation. Seven o'clock had just struck, and M. Bertuccio, according to the command which had been given him, had two hours before left for Auteuil, when a cab stopped at the door, and after depositing its occupant at the gate, immediately hurried away, as if ashamed of its employment. The visitor was about fifty-two years of age, dressed in one of the green surtouts, ornamented with black frogs, which have so long maintained their popularity all over Europe. He wore trousers of blue cloth, boots tolerably clean, but not of the brightest polish, and a little too thick in the soles, buckskin gloves, a hat somewhat resembling in shape those usually worn by the gendarmes, and a black cravat striped with white, which, if the proprietor had not worn it of his own free will, might have passed for a halter, so much did it resemble one. Such was the picturesque costume of the person who rang at the gate, and demanded if it was not at No. 30 in the Avenue des Champs-Elysees that the Count of Monte Cristo lived, and who, being answered by the porter in the affirmative, entered, closed the gate after him, and began to ascend the steps.`
|
monteCristo = `Both the count and Baptistin had told the truth when they announced to Morcerf the proposed visit of the major, which had served Monte Cristo as a pretext for declining Albert's invitation. Seven o'clock had just struck, and M. Bertuccio, according to the command which had been given him, had two hours before left for Auteuil, when a cab stopped at the door, and after depositing its occupant at the gate, immediately hurried away, as if ashamed of its employment. The visitor was about fifty-two years of age, dressed in one of the green surtouts, ornamented with black frogs, which have so long maintained their popularity all over Europe. He wore trousers of blue cloth, boots tolerably clean, but not of the brightest polish, and a little too thick in the soles, buckskin gloves, a hat somewhat resembling in shape those usually worn by the gendarmes, and a black cravat striped with white, which, if the proprietor had not worn it of his own free will, might have passed for a halter, so much did it resemble one. Such was the picturesque costume of the person who rang at the gate, and demanded if it was not at No. 30 in the Avenue des Champs-Elysees that the Count of Monte Cristo lived, and who, being answered by the porter in the affirmative, entered, closed the gate after him, and began to ascend the steps.`
|
||||||
)
|
)
|
||||||
|
|
||||||
func assertWrapCorrect(text string, lineWidth int, allowSplitWords bool, t *testing.T) {
|
|
||||||
lines := WordWrap(text, lineWidth)
|
|
||||||
|
|
||||||
reconstructed := strings.Join(lines, "")
|
|
||||||
if text != reconstructed {
|
|
||||||
t.Errorf("text %v does not match original %v", text, reconstructed)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, line := range lines {
|
|
||||||
if len(line) > lineWidth {
|
|
||||||
t.Errorf("line too long: %d, %v", len(line), line)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !allowSplitWords {
|
|
||||||
origWords := strings.Fields(text)
|
|
||||||
var newWords []string
|
|
||||||
for _, line := range lines {
|
|
||||||
newWords = append(newWords, strings.Fields(line)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !reflect.DeepEqual(origWords, newWords) {
|
|
||||||
t.Errorf("words %v do not match wrapped words %v", origWords, newWords)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWordWrap(t *testing.T) {
|
|
||||||
assertWrapCorrect("jackdaws love my big sphinx of quartz", 12, false, t)
|
|
||||||
// long word that will necessarily be split:
|
|
||||||
assertWrapCorrect("jackdawslovemybigsphinxofquartz", 12, true, t)
|
|
||||||
|
|
||||||
assertWrapCorrect(threeMusketeers, 40, true, t)
|
|
||||||
assertWrapCorrect(monteCristo, 20, false, t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkWordWrap(b *testing.B) {
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
WordWrap(threeMusketeers, 40)
|
|
||||||
WordWrap(monteCristo, 60)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTokenLineBuilder(t *testing.T) {
|
func TestTokenLineBuilder(t *testing.T) {
|
||||||
lineLen := 400
|
lineLen := 400
|
||||||
var tl TokenLineBuilder
|
var tl TokenLineBuilder
|
||||||
|
@ -586,13 +586,6 @@ limits:
|
|||||||
# maximum length of channel lists (beI modes)
|
# maximum length of channel lists (beI modes)
|
||||||
chan-list-modes: 60
|
chan-list-modes: 60
|
||||||
|
|
||||||
# maximum length of IRC lines
|
|
||||||
# this should generally be 1024-2048, and will only apply when negotiated by clients
|
|
||||||
linelen:
|
|
||||||
# ratified version of the message-tags cap fixes the max tag length at 8191 bytes
|
|
||||||
# configurable length for the rest of the message:
|
|
||||||
rest: 2048
|
|
||||||
|
|
||||||
# maximum number of messages to accept during registration (prevents
|
# maximum number of messages to accept during registration (prevents
|
||||||
# DoS / resource exhaustion attacks):
|
# DoS / resource exhaustion attacks):
|
||||||
registration-messages: 1024
|
registration-messages: 1024
|
||||||
|
Loading…
Reference in New Issue
Block a user