3
0
mirror of https://github.com/ergochat/ergo.git synced 2024-11-14 16:09:32 +01:00

upgrade message-tags to non-draft version

This commit is contained in:
Shivaram Lingamneni 2019-03-07 02:31:46 -05:00
parent acd9eeeb15
commit 85493ef031
19 changed files with 200 additions and 313 deletions

4
Gopkg.lock generated
View File

@ -27,7 +27,7 @@
[[projects]] [[projects]]
branch = "master" branch = "master"
digest = "1:6bcd7bcd5e14cc9552fbf83b2f77f24935c0c502d009c9825b6c212c3f8eb967" digest = "1:e6ed6eaa63211bb90847d8c5f11d7412e56c96b5befb7402ee7a7a8ad02700ec"
name = "github.com/goshuirc/irc-go" name = "github.com/goshuirc/irc-go"
packages = [ packages = [
"ircfmt", "ircfmt",
@ -35,7 +35,7 @@
"ircmsg", "ircmsg",
] ]
pruneopts = "UT" pruneopts = "UT"
revision = "cf199aea7186fd960d0ed5abbf579bb0f9d890d1" revision = "ca74bf6a176d2d1dce6f28f99901a2d48d8da2bd"
[[projects]] [[projects]]
digest = "1:c658e84ad3916da105a761660dcaeb01e63416c8ec7bc62256a9b411a05fcd67" digest = "1:c658e84ad3916da105a761660dcaeb01e63416c8ec7bc62256a9b411a05fcd67"

View File

@ -83,15 +83,15 @@ CAPDEFS = [
), ),
CapDef( CapDef(
identifier="MaxLine", identifier="MaxLine",
name="oragono.io/maxline", name="oragono.io/maxline-2",
url="https://oragono.io/maxline", url="https://oragono.io/maxline-2",
standard="Oragono-specific", standard="Oragono-specific",
), ),
CapDef( CapDef(
identifier="MessageTags", identifier="MessageTags",
name="draft/message-tags-0.2", name="message-tags",
url="https://ircv3.net/specs/core/message-tags-3.3.html", url="https://ircv3.net/specs/extensions/message-tags.html",
standard="draft IRCv3", standard="IRCv3",
), ),
CapDef( CapDef(
identifier="MultiPrefix", identifier="MultiPrefix",

View File

@ -57,12 +57,12 @@ const (
// https://gist.github.com/DanielOaks/8126122f74b26012a3de37db80e4e0c6 // https://gist.github.com/DanielOaks/8126122f74b26012a3de37db80e4e0c6
Languages Capability = iota Languages Capability = iota
// MaxLine is the Oragono-specific capability named "oragono.io/maxline": // MaxLine is the Oragono-specific capability named "oragono.io/maxline-2":
// https://oragono.io/maxline // https://oragono.io/maxline-2
MaxLine Capability = iota MaxLine Capability = iota
// MessageTags is the draft IRCv3 capability named "draft/message-tags-0.2": // MessageTags is the IRCv3 capability named "message-tags":
// https://ircv3.net/specs/core/message-tags-3.3.html // https://ircv3.net/specs/extensions/message-tags.html
MessageTags Capability = iota MessageTags Capability = iota
// MultiPrefix is the IRCv3 capability named "multi-prefix": // MultiPrefix is the IRCv3 capability named "multi-prefix":
@ -112,8 +112,8 @@ var (
"invite-notify", "invite-notify",
"draft/labeled-response", "draft/labeled-response",
"draft/languages", "draft/languages",
"oragono.io/maxline", "oragono.io/maxline-2",
"draft/message-tags-0.2", "message-tags",
"multi-prefix", "multi-prefix",
"draft/rename", "draft/rename",
"draft/resume-0.3", "draft/resume-0.3",

View File

@ -14,7 +14,6 @@ import (
"sync" "sync"
"github.com/goshuirc/irc-go/ircmsg"
"github.com/oragono/oragono/irc/caps" "github.com/oragono/oragono/irc/caps"
"github.com/oragono/oragono/irc/history" "github.com/oragono/oragono/irc/history"
"github.com/oragono/oragono/irc/modes" "github.com/oragono/oragono/irc/modes"
@ -425,11 +424,13 @@ func (channel *Channel) Join(client *Client, key string, isSajoin bool, rb *Resp
channel.regenerateMembersCache() channel.regenerateMembersCache()
message := utils.SplitMessage{}
message.Msgid = details.realname
channel.history.Add(history.Item{ channel.history.Add(history.Item{
Type: history.Join, Type: history.Join,
Nick: details.nickMask, Nick: details.nickMask,
AccountName: details.accountName, AccountName: details.accountName,
Msgid: details.realname, Message: message,
}) })
return return
@ -603,16 +604,17 @@ func (channel *Channel) replayHistoryItems(rb *ResponseBuffer, items []history.I
serverTime := client.capabilities.Has(caps.ServerTime) serverTime := client.capabilities.Has(caps.ServerTime)
for _, item := range items { for _, item := range items {
var tags Tags var tags map[string]string
if serverTime { if serverTime {
tags = ensureTag(tags, "time", item.Time.Format(IRCv3TimestampFormat)) tags = map[string]string{"time": item.Time.Format(IRCv3TimestampFormat)}
} }
// TODO(#437) support history.Tagmsg
switch item.Type { switch item.Type {
case history.Privmsg: case history.Privmsg:
rb.AddSplitMessageFromClient(item.Msgid, item.Nick, item.AccountName, tags, "PRIVMSG", chname, item.Message) rb.AddSplitMessageFromClient(item.Nick, item.AccountName, tags, "PRIVMSG", chname, item.Message)
case history.Notice: case history.Notice:
rb.AddSplitMessageFromClient(item.Msgid, item.Nick, item.AccountName, tags, "NOTICE", chname, item.Message) rb.AddSplitMessageFromClient(item.Nick, item.AccountName, tags, "NOTICE", chname, item.Message)
case history.Join: case history.Join:
nick := stripMaskFromNick(item.Nick) nick := stripMaskFromNick(item.Nick)
var message string var message string
@ -624,16 +626,16 @@ func (channel *Channel) replayHistoryItems(rb *ResponseBuffer, items []history.I
rb.Add(tags, "HistServ", "PRIVMSG", chname, message) rb.Add(tags, "HistServ", "PRIVMSG", chname, message)
case history.Part: case history.Part:
nick := stripMaskFromNick(item.Nick) nick := stripMaskFromNick(item.Nick)
message := fmt.Sprintf(client.t("%[1]s left the channel (%[2]s)"), nick, item.Message.Original) message := fmt.Sprintf(client.t("%[1]s left the channel (%[2]s)"), nick, item.Message.Message)
rb.Add(tags, "HistServ", "PRIVMSG", chname, message) rb.Add(tags, "HistServ", "PRIVMSG", chname, message)
case history.Quit: case history.Quit:
nick := stripMaskFromNick(item.Nick) nick := stripMaskFromNick(item.Nick)
message := fmt.Sprintf(client.t("%[1]s quit (%[2]s)"), nick, item.Message.Original) message := fmt.Sprintf(client.t("%[1]s quit (%[2]s)"), nick, item.Message.Message)
rb.Add(tags, "HistServ", "PRIVMSG", chname, message) rb.Add(tags, "HistServ", "PRIVMSG", chname, message)
case history.Kick: case history.Kick:
nick := stripMaskFromNick(item.Nick) nick := stripMaskFromNick(item.Nick)
// XXX Msgid is the kick target // XXX Msgid is the kick target
message := fmt.Sprintf(client.t("%[1]s kicked %[2]s (%[3]s)"), nick, item.Msgid, item.Message.Original) message := fmt.Sprintf(client.t("%[1]s kicked %[2]s (%[3]s)"), nick, item.Message.Msgid, item.Message.Message)
rb.Add(tags, "HistServ", "PRIVMSG", chname, message) rb.Add(tags, "HistServ", "PRIVMSG", chname, message)
} }
} }
@ -717,13 +719,20 @@ func (channel *Channel) CanSpeak(client *Client) bool {
return true return true
} }
// TagMsg sends a tag message to everyone in this channel who can accept them. func (channel *Channel) SendSplitMessage(command string, minPrefix *modes.Mode, clientOnlyTags map[string]string, client *Client, message utils.SplitMessage, rb *ResponseBuffer) {
func (channel *Channel) TagMsg(msgid string, minPrefix *modes.Mode, clientOnlyTags *map[string]ircmsg.TagValue, client *Client, rb *ResponseBuffer) { var histType history.ItemType
channel.sendMessage(msgid, "TAGMSG", []caps.Capability{caps.MessageTags}, minPrefix, clientOnlyTags, client, nil, rb) switch command {
} case "PRIVMSG":
histType = history.Privmsg
case "NOTICE":
histType = history.Notice
case "TAGMSG":
histType = history.Tagmsg
default:
channel.server.logger.Error("internal", "unrecognized Channel.SendSplitMessage command", command)
return
}
// sendMessage sends a given message to everyone on this channel.
func (channel *Channel) sendMessage(msgid, cmd string, requiredCaps []caps.Capability, minPrefix *modes.Mode, clientOnlyTags *map[string]ircmsg.TagValue, client *Client, message *string, rb *ResponseBuffer) {
if !channel.CanSpeak(client) { if !channel.CanSpeak(client) {
rb.Add(nil, client.server.name, ERR_CANNOTSENDTOCHAN, channel.name, client.t("Cannot send to channel")) rb.Add(nil, client.server.name, ERR_CANNOTSENDTOCHAN, channel.name, client.t("Cannot send to channel"))
return return
@ -736,85 +745,16 @@ func (channel *Channel) sendMessage(msgid, cmd string, requiredCaps []caps.Capab
} }
// send echo-message // send echo-message
if client.capabilities.Has(caps.EchoMessage) { if client.capabilities.Has(caps.EchoMessage) {
var messageTagsToUse *map[string]ircmsg.TagValue var tagsToUse map[string]string
if client.capabilities.Has(caps.MessageTags) {
messageTagsToUse = clientOnlyTags
}
nickMaskString := client.NickMaskString()
accountName := client.AccountName()
if message == nil {
rb.AddFromClient(msgid, nickMaskString, accountName, messageTagsToUse, cmd, channel.name)
} else {
rb.AddFromClient(msgid, nickMaskString, accountName, messageTagsToUse, cmd, channel.name, *message)
}
}
for _, member := range channel.Members() {
if minPrefix != nil && !channel.ClientIsAtLeast(member, minPrefixMode) {
// STATUSMSG
continue
}
// echo-message is handled above, so skip sending the msg to the user themselves as well
if member == client {
continue
}
canReceive := true
for _, capName := range requiredCaps {
if !member.capabilities.Has(capName) {
canReceive = false
}
}
if !canReceive {
continue
}
var messageTagsToUse *map[string]ircmsg.TagValue
if member.capabilities.Has(caps.MessageTags) {
messageTagsToUse = clientOnlyTags
}
if message == nil {
member.SendFromClient(msgid, client, messageTagsToUse, cmd, channel.name)
} else {
member.SendFromClient(msgid, client, messageTagsToUse, cmd, channel.name, *message)
}
}
}
// SplitPrivMsg sends a private message to everyone in this channel.
func (channel *Channel) SplitPrivMsg(msgid string, minPrefix *modes.Mode, clientOnlyTags *map[string]ircmsg.TagValue, client *Client, message utils.SplitMessage, rb *ResponseBuffer) {
channel.sendSplitMessage(msgid, "PRIVMSG", history.Privmsg, minPrefix, clientOnlyTags, client, &message, rb)
}
// SplitNotice sends a private message to everyone in this channel.
func (channel *Channel) SplitNotice(msgid string, minPrefix *modes.Mode, clientOnlyTags *map[string]ircmsg.TagValue, client *Client, message utils.SplitMessage, rb *ResponseBuffer) {
channel.sendSplitMessage(msgid, "NOTICE", history.Notice, minPrefix, clientOnlyTags, client, &message, rb)
}
func (channel *Channel) sendSplitMessage(msgid, cmd string, histType history.ItemType, minPrefix *modes.Mode, clientOnlyTags *map[string]ircmsg.TagValue, client *Client, message *utils.SplitMessage, rb *ResponseBuffer) {
if !channel.CanSpeak(client) {
rb.Add(nil, client.server.name, ERR_CANNOTSENDTOCHAN, channel.name, client.t("Cannot send to channel"))
return
}
// for STATUSMSG
var minPrefixMode modes.Mode
if minPrefix != nil {
minPrefixMode = *minPrefix
}
// send echo-message
if client.capabilities.Has(caps.EchoMessage) {
var tagsToUse *map[string]ircmsg.TagValue
if client.capabilities.Has(caps.MessageTags) { if client.capabilities.Has(caps.MessageTags) {
tagsToUse = clientOnlyTags tagsToUse = clientOnlyTags
} }
nickMaskString := client.NickMaskString() nickMaskString := client.NickMaskString()
accountName := client.AccountName() accountName := client.AccountName()
if message == nil { if command == "TAGMSG" && client.capabilities.Has(caps.MessageTags) {
rb.AddFromClient(msgid, nickMaskString, accountName, tagsToUse, cmd, channel.name) rb.AddFromClient(message.Msgid, nickMaskString, accountName, tagsToUse, command, channel.name)
} else { } else {
rb.AddSplitMessageFromClient(msgid, nickMaskString, accountName, tagsToUse, cmd, channel.name, *message) rb.AddSplitMessageFromClient(nickMaskString, accountName, tagsToUse, command, channel.name, message)
} }
} }
@ -832,22 +772,23 @@ func (channel *Channel) sendSplitMessage(msgid, cmd string, histType history.Ite
if member == client { if member == client {
continue continue
} }
var tagsToUse *map[string]ircmsg.TagValue var tagsToUse map[string]string
if member.capabilities.Has(caps.MessageTags) { if member.capabilities.Has(caps.MessageTags) {
tagsToUse = clientOnlyTags tagsToUse = clientOnlyTags
} else if command == "TAGMSG" {
continue
} }
if message == nil { if command == "TAGMSG" {
member.sendFromClientInternal(false, now, msgid, nickmask, account, tagsToUse, cmd, channel.name) member.sendFromClientInternal(false, now, message.Msgid, nickmask, account, tagsToUse, command, channel.name)
} else { } else {
member.sendSplitMsgFromClientInternal(false, now, msgid, nickmask, account, tagsToUse, cmd, channel.name, *message) member.sendSplitMsgFromClientInternal(false, now, nickmask, account, tagsToUse, command, channel.name, message)
} }
} }
channel.history.Add(history.Item{ channel.history.Add(history.Item{
Type: histType, Type: histType,
Msgid: msgid, Message: message,
Message: *message,
Nick: nickmask, Nick: nickmask,
AccountName: account, AccountName: account,
Time: now, Time: now,
@ -980,12 +921,14 @@ func (channel *Channel) Kick(client *Client, target *Client, comment string, rb
member.Send(nil, clientMask, "KICK", channel.name, targetNick, comment) member.Send(nil, clientMask, "KICK", channel.name, targetNick, comment)
} }
message := utils.SplitMessage{}
message.Message = comment
message.Msgid = targetNick // XXX abuse this field
channel.history.Add(history.Item{ channel.history.Add(history.Item{
Type: history.Kick, Type: history.Kick,
Nick: clientMask, Nick: clientMask,
Message: utils.MakeSplitMessage(comment, true),
AccountName: target.AccountName(), AccountName: target.AccountName(),
Msgid: targetNick, // XXX abuse this field Message: message,
}) })
channel.Quit(target) channel.Quit(target)

View File

@ -62,14 +62,13 @@ type Client struct {
hasQuit bool hasQuit bool
hops int hops int
hostname string hostname string
idletimer *IdleTimer idletimer IdleTimer
invitedTo map[string]bool invitedTo map[string]bool
isDestroyed bool isDestroyed bool
isTor bool isTor bool
isQuitting bool isQuitting bool
languages []string languages []string
loginThrottle connection_limits.GenericThrottle loginThrottle connection_limits.GenericThrottle
maxlenTags uint32
maxlenRest uint32 maxlenRest uint32
nick string nick string
nickCasefolded string nickCasefolded string
@ -122,8 +121,9 @@ type ClientDetails struct {
func RunNewClient(server *Server, conn clientConn) { func RunNewClient(server *Server, conn clientConn) {
now := time.Now() now := time.Now()
config := server.Config() config := server.Config()
fullLineLenLimit := config.Limits.LineLen.Tags + config.Limits.LineLen.Rest fullLineLenLimit := ircmsg.MaxlenTagsFromClient + config.Limits.LineLen.Rest
socket := NewSocket(conn.Conn, fullLineLenLimit*2, config.Server.MaxSendQBytes) // give them 1k of grace over the limit:
socket := NewSocket(conn.Conn, fullLineLenLimit+1024, config.Server.MaxSendQBytes)
client := &Client{ client := &Client{
atime: now, atime: now,
capabilities: caps.NewSet(), capabilities: caps.NewSet(),
@ -260,30 +260,21 @@ func (client *Client) IPString() string {
// command goroutine // command goroutine
// //
func (client *Client) recomputeMaxlens() (int, int) { func (client *Client) recomputeMaxlens() int {
maxlenTags := 512
maxlenRest := 512 maxlenRest := 512
if client.capabilities.Has(caps.MessageTags) {
maxlenTags = 4096
}
if client.capabilities.Has(caps.MaxLine) { if client.capabilities.Has(caps.MaxLine) {
limits := client.server.Limits() maxlenRest = client.server.Limits().LineLen.Rest
if limits.LineLen.Tags > maxlenTags {
maxlenTags = limits.LineLen.Tags
}
maxlenRest = limits.LineLen.Rest
} }
atomic.StoreUint32(&client.maxlenTags, uint32(maxlenTags))
atomic.StoreUint32(&client.maxlenRest, uint32(maxlenRest)) atomic.StoreUint32(&client.maxlenRest, uint32(maxlenRest))
return maxlenTags, maxlenRest return maxlenRest
} }
// allow these negotiated length limits to be read without locks; this is a convenience // allow these negotiated length limits to be read without locks; this is a convenience
// so that Client.Send doesn't have to acquire any Client locks // so that Client.Send doesn't have to acquire any Client locks
func (client *Client) maxlens() (int, int) { func (client *Client) MaxlenRest() int {
return int(atomic.LoadUint32(&client.maxlenTags)), int(atomic.LoadUint32(&client.maxlenRest)) return int(atomic.LoadUint32(&client.maxlenRest))
} }
func (client *Client) run() { func (client *Client) run() {
@ -306,8 +297,7 @@ func (client *Client) run() {
client.destroy(false) client.destroy(false)
}() }()
client.idletimer = NewIdleTimer(client) client.idletimer.Initialize(client)
client.idletimer.Start()
client.nickTimer = NewNickTimer(client) client.nickTimer = NewNickTimer(client)
@ -316,7 +306,7 @@ func (client *Client) run() {
firstLine := true firstLine := true
for { for {
maxlenTags, maxlenRest := client.recomputeMaxlens() maxlenRest := client.recomputeMaxlens()
line, err = client.socket.Read() line, err = client.socket.Read()
if err != nil { if err != nil {
@ -345,9 +335,12 @@ func (client *Client) run() {
} }
} }
msg, err = ircmsg.ParseLineMaxLen(line, maxlenTags, maxlenRest) msg, err = ircmsg.ParseLineStrict(line, true, maxlenRest)
if err == ircmsg.ErrorLineIsEmpty { if err == ircmsg.ErrorLineIsEmpty {
continue continue
} else if err == ircmsg.ErrorLineTooLong {
client.Send(nil, client.server.name, ERR_INPUTTOOLONG, client.nick, client.t("Input line too long"))
continue
} else if err != nil { } else if err != nil {
client.Quit(client.t("Received malformed line")) client.Quit(client.t("Received malformed line"))
break break
@ -553,11 +546,11 @@ func (client *Client) replayPrivmsgHistory(rb *ResponseBuffer, items []history.I
default: default:
continue continue
} }
var tags Tags var tags map[string]string
if serverTime { if serverTime {
tags = ensureTag(tags, "time", item.Time.Format(IRCv3TimestampFormat)) tags = map[string]string{"time": item.Time.Format(IRCv3TimestampFormat)}
} }
rb.AddSplitMessageFromClient(item.Msgid, item.Nick, item.AccountName, tags, command, nick, item.Message) rb.AddSplitMessageFromClient(item.Nick, item.AccountName, tags, command, nick, item.Message)
} }
if !complete { if !complete {
rb.Add(nil, "HistServ", "NOTICE", nick, client.t("Some additional message history may have been lost")) rb.Add(nil, "HistServ", "NOTICE", nick, client.t("Some additional message history may have been lost"))
@ -855,17 +848,18 @@ func (client *Client) Quit(message string) {
return return
} }
var quitLine string var finalData []byte
// #364: don't send QUIT lines to unregistered clients // #364: don't send QUIT lines to unregistered clients
if registered { if registered {
quitMsg := ircmsg.MakeMessage(nil, prefix, "QUIT", message) quitMsg := ircmsg.MakeMessage(nil, prefix, "QUIT", message)
quitLine, _ = quitMsg.Line() finalData, _ = quitMsg.LineBytesStrict(false, 512)
} }
errorMsg := ircmsg.MakeMessage(nil, "", "ERROR", message) errorMsg := ircmsg.MakeMessage(nil, "", "ERROR", message)
errorLine, _ := errorMsg.Line() errorMsgBytes, _ := errorMsg.LineBytesStrict(false, 512)
finalData = append(finalData, errorMsgBytes...)
client.socket.SetFinalData(quitLine + errorLine) client.socket.SetFinalData(finalData)
} }
// destroy gets rid of a client, removes them from server lists etc. // destroy gets rid of a client, removes them from server lists etc.
@ -976,50 +970,45 @@ func (client *Client) destroy(beingResumed bool) {
// 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 (client *Client) SendSplitMsgFromClient(msgid string, from *Client, tags Tags, command, target string, message utils.SplitMessage) { func (client *Client) SendSplitMsgFromClient(from *Client, tags map[string]string, command, target string, message utils.SplitMessage) {
client.sendSplitMsgFromClientInternal(false, time.Time{}, msgid, from.NickMaskString(), from.AccountName(), tags, command, target, message) client.sendSplitMsgFromClientInternal(false, time.Time{}, from.NickMaskString(), from.AccountName(), tags, command, target, message)
} }
func (client *Client) sendSplitMsgFromClientInternal(blocking bool, serverTime time.Time, msgid string, nickmask, accountName string, tags Tags, command, target string, message utils.SplitMessage) { func (client *Client) sendSplitMsgFromClientInternal(blocking bool, serverTime time.Time, nickmask, accountName string, tags map[string]string, command, target string, message utils.SplitMessage) {
if client.capabilities.Has(caps.MaxLine) || message.Wrapped == nil { if client.capabilities.Has(caps.MaxLine) || message.Wrapped == nil {
client.sendFromClientInternal(blocking, serverTime, msgid, nickmask, accountName, tags, command, target, message.Original) client.sendFromClientInternal(blocking, serverTime, message.Msgid, nickmask, accountName, tags, command, target, message.Message)
} else { } else {
for _, str := range message.Wrapped { for _, messagePair := range message.Wrapped {
client.sendFromClientInternal(blocking, serverTime, msgid, nickmask, accountName, tags, command, target, str) client.sendFromClientInternal(blocking, serverTime, messagePair.Msgid, nickmask, accountName, tags, command, target, messagePair.Message)
} }
} }
} }
// SendFromClient sends an IRC line coming from a specific client. // SendFromClient sends an IRC line coming from a specific client.
// Adds account-tag to the line as well. // Adds account-tag to the line as well.
func (client *Client) SendFromClient(msgid string, from *Client, tags Tags, command string, params ...string) error { func (client *Client) SendFromClient(msgid string, from *Client, tags map[string]string, command string, params ...string) error {
return client.sendFromClientInternal(false, time.Time{}, msgid, from.NickMaskString(), from.AccountName(), tags, command, params...) return client.sendFromClientInternal(false, time.Time{}, msgid, from.NickMaskString(), from.AccountName(), tags, command, params...)
} }
// helper to add a tag to `tags` (or create a new tag set if the current one is nil) // this is SendFromClient, but directly exposing nickmask and accountName,
func ensureTag(tags Tags, tagName, tagValue string) (result Tags) { // for things like history replay and CHGHOST where they no longer (necessarily)
if tags == nil { // correspond to the current state of a client
result = ircmsg.MakeTags(tagName, tagValue) func (client *Client) sendFromClientInternal(blocking bool, serverTime time.Time, msgid string, nickmask, accountName string, tags map[string]string, command string, params ...string) error {
} else { msg := ircmsg.MakeMessage(tags, nickmask, command, params...)
result = tags
(*tags)[tagName] = ircmsg.MakeTagValue(tagValue)
}
return
}
// XXX this is a hack where we allow overriding the client's nickmask
// this is to support CHGHOST, which requires that we send the *original* nickmask with the response
func (client *Client) sendFromClientInternal(blocking bool, serverTime time.Time, msgid string, nickmask, accountName string, tags Tags, command string, params ...string) error {
// attach account-tag // attach account-tag
if client.capabilities.Has(caps.AccountTag) && accountName != "*" { if client.capabilities.Has(caps.AccountTag) && accountName != "*" {
tags = ensureTag(tags, "account", accountName) msg.SetTag("account", accountName)
} }
// attach message-id // attach message-id
if len(msgid) > 0 && client.capabilities.Has(caps.MessageTags) { if msgid != "" && client.capabilities.Has(caps.MessageTags) {
tags = ensureTag(tags, "draft/msgid", msgid) msg.SetTag("draft/msgid", msgid)
}
// attach server-time
if client.capabilities.Has(caps.ServerTime) {
msg.SetTag("time", time.Now().UTC().Format(IRCv3TimestampFormat))
} }
return client.sendInternal(blocking, serverTime, tags, nickmask, command, params...) return client.SendRawMessage(msg, blocking)
} }
var ( var (
@ -1039,24 +1028,24 @@ var (
func (client *Client) SendRawMessage(message ircmsg.IrcMessage, blocking bool) error { func (client *Client) SendRawMessage(message ircmsg.IrcMessage, blocking bool) error {
// use dumb hack to force the last param to be a trailing param if required // use dumb hack to force the last param to be a trailing param if required
var usedTrailingHack bool var usedTrailingHack bool
if commandsThatMustUseTrailing[strings.ToUpper(message.Command)] && len(message.Params) > 0 { if commandsThatMustUseTrailing[message.Command] && len(message.Params) > 0 {
lastParam := message.Params[len(message.Params)-1] lastParam := message.Params[len(message.Params)-1]
// to force trailing, we ensure the final param contains a space // to force trailing, we ensure the final param contains a space
if !strings.Contains(lastParam, " ") { if strings.IndexByte(lastParam, ' ') == -1 {
message.Params[len(message.Params)-1] = lastParam + " " message.Params[len(message.Params)-1] = lastParam + " "
usedTrailingHack = true usedTrailingHack = true
} }
} }
// assemble message // assemble message
maxlenTags, maxlenRest := client.maxlens() maxlenRest := client.MaxlenRest()
line, err := message.LineMaxLenBytes(maxlenTags, maxlenRest) 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())
client.server.logger.Error("internal", logline) client.server.logger.Error("internal", logline)
message = ircmsg.MakeMessage(nil, client.server.name, ERR_UNKNOWNERROR, "*", "Error assembling message for sending") message = ircmsg.MakeMessage(nil, client.server.name, ERR_UNKNOWNERROR, "*", "Error assembling message for sending")
line, _ := message.LineBytes() line, _ := message.LineBytesStrict(false, 0)
if blocking { if blocking {
client.socket.BlockingWrite(line) client.socket.BlockingWrite(line)
@ -1068,7 +1057,7 @@ func (client *Client) SendRawMessage(message ircmsg.IrcMessage, blocking bool) e
// if we used the trailing hack, we need to strip the final space we appended earlier on // if we used the trailing hack, we need to strip the final space we appended earlier on
if usedTrailingHack { if usedTrailingHack {
copy(line[len(line)-3:], []byte{'\r', '\n'}) copy(line[len(line)-3:], "\r\n")
line = line[:len(line)-1] line = line[:len(line)-1]
} }
@ -1084,24 +1073,13 @@ func (client *Client) SendRawMessage(message ircmsg.IrcMessage, blocking bool) e
} }
} }
func (client *Client) sendInternal(blocking bool, serverTime time.Time, tags Tags, prefix string, command string, params ...string) error {
// attach server time
if client.capabilities.Has(caps.ServerTime) {
if serverTime.IsZero() {
serverTime = time.Now()
}
tags = ensureTag(tags, "time", serverTime.UTC().Format(IRCv3TimestampFormat))
}
// send out the message
message := ircmsg.MakeMessage(tags, prefix, command, params...)
client.SendRawMessage(message, blocking)
return nil
}
// Send sends an IRC line to the client. // Send sends an IRC line to the client.
func (client *Client) Send(tags Tags, prefix string, command string, params ...string) error { func (client *Client) Send(tags map[string]string, prefix string, command string, params ...string) error {
return client.sendInternal(false, time.Time{}, tags, prefix, command, params...) msg := ircmsg.MakeMessage(tags, prefix, command, params...)
if client.capabilities.Has(caps.ServerTime) && !msg.HasTag("time") {
msg.SetTag("time", time.Now().UTC().Format(IRCv3TimestampFormat))
}
return client.SendRawMessage(msg, false)
} }
// Notice sends the client a notice from the server. // Notice sends the client a notice from the server.

View File

@ -202,7 +202,6 @@ type OperConfig struct {
// LineLenConfig controls line lengths. // LineLenConfig controls line lengths.
type LineLenLimits struct { type LineLenLimits struct {
Tags int
Rest int Rest int
} }
@ -553,9 +552,6 @@ func LoadConfig(filename string) (config *Config, err error) {
} }
config.Server.WebIRC = newWebIRC config.Server.WebIRC = newWebIRC
// process limits // process limits
if config.Limits.LineLen.Tags < 512 {
config.Limits.LineLen.Tags = 512
}
if config.Limits.LineLen.Rest < 512 { if config.Limits.LineLen.Rest < 512 {
config.Limits.LineLen.Rest = 512 config.Limits.LineLen.Rest = 512
} }

View File

@ -1874,7 +1874,7 @@ func nickHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Resp
// NOTICE <target>{,<target>} <message> // NOTICE <target>{,<target>} <message>
func noticeHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool { func noticeHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool {
clientOnlyTags := utils.GetClientOnlyTags(msg.Tags) clientOnlyTags := msg.ClientOnlyTags()
targets := strings.Split(msg.Params[0], ",") targets := strings.Split(msg.Params[0], ",")
message := msg.Params[1] message := msg.Params[1]
@ -1883,7 +1883,6 @@ func noticeHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Re
return false return false
} }
// split privmsg
splitMsg := utils.MakeSplitMessage(message, !client.capabilities.Has(caps.MaxLine)) splitMsg := utils.MakeSplitMessage(message, !client.capabilities.Has(caps.MaxLine))
for i, targetString := range targets { for i, targetString := range targets {
@ -1905,8 +1904,7 @@ func noticeHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Re
// errors silently ignored with NOTICE as per RFC // errors silently ignored with NOTICE as per RFC
continue continue
} }
msgid := server.generateMessageID() channel.SendSplitMessage("NOTICE", lowestPrefix, clientOnlyTags, client, splitMsg, rb)
channel.SplitNotice(msgid, lowestPrefix, clientOnlyTags, client, splitMsg, rb)
} else { } else {
target, err := CasefoldName(targetString) target, err := CasefoldName(targetString)
if err != nil { if err != nil {
@ -1926,23 +1924,21 @@ func noticeHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Re
if !user.capabilities.Has(caps.MessageTags) { if !user.capabilities.Has(caps.MessageTags) {
clientOnlyTags = nil clientOnlyTags = nil
} }
msgid := server.generateMessageID()
// restrict messages appropriately when +R is set // restrict messages appropriately when +R is set
// intentionally make the sending user think the message went through fine // intentionally make the sending user think the message went through fine
allowedPlusR := !user.HasMode(modes.RegisteredOnly) || client.LoggedIntoAccount() allowedPlusR := !user.HasMode(modes.RegisteredOnly) || client.LoggedIntoAccount()
allowedTor := !user.isTor || !isRestrictedCTCPMessage(message) allowedTor := !user.isTor || !isRestrictedCTCPMessage(message)
if allowedPlusR && allowedTor { if allowedPlusR && allowedTor {
user.SendSplitMsgFromClient(msgid, client, clientOnlyTags, "NOTICE", user.nick, splitMsg) user.SendSplitMsgFromClient(client, clientOnlyTags, "NOTICE", user.nick, splitMsg)
} }
nickMaskString := client.NickMaskString() nickMaskString := client.NickMaskString()
accountName := client.AccountName() accountName := client.AccountName()
if client.capabilities.Has(caps.EchoMessage) { if client.capabilities.Has(caps.EchoMessage) {
rb.AddSplitMessageFromClient(msgid, nickMaskString, accountName, clientOnlyTags, "NOTICE", user.nick, splitMsg) rb.AddSplitMessageFromClient(nickMaskString, accountName, clientOnlyTags, "NOTICE", user.nick, splitMsg)
} }
user.history.Add(history.Item{ user.history.Add(history.Item{
Type: history.Notice, Type: history.Notice,
Msgid: msgid,
Message: splitMsg, Message: splitMsg,
Nick: nickMaskString, Nick: nickMaskString,
AccountName: accountName, AccountName: accountName,
@ -2096,7 +2092,7 @@ func isRestrictedCTCPMessage(message string) bool {
// PRIVMSG <target>{,<target>} <message> // PRIVMSG <target>{,<target>} <message>
func privmsgHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool { func privmsgHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool {
clientOnlyTags := utils.GetClientOnlyTags(msg.Tags) clientOnlyTags := msg.ClientOnlyTags()
targets := strings.Split(msg.Params[0], ",") targets := strings.Split(msg.Params[0], ",")
message := msg.Params[1] message := msg.Params[1]
@ -2133,8 +2129,7 @@ func privmsgHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *R
rb.Add(nil, client.server.name, ERR_CANNOTSENDTOCHAN, channel.name, client.t("Cannot send to channel")) rb.Add(nil, client.server.name, ERR_CANNOTSENDTOCHAN, channel.name, client.t("Cannot send to channel"))
continue continue
} }
msgid := server.generateMessageID() channel.SendSplitMessage("PRIVMSG", lowestPrefix, clientOnlyTags, client, splitMsg, rb)
channel.SplitPrivMsg(msgid, lowestPrefix, clientOnlyTags, client, splitMsg, rb)
} else { } else {
target, err = CasefoldName(targetString) target, err = CasefoldName(targetString)
if service, isService := OragonoServices[target]; isService { if service, isService := OragonoServices[target]; isService {
@ -2151,18 +2146,17 @@ func privmsgHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *R
if !user.capabilities.Has(caps.MessageTags) { if !user.capabilities.Has(caps.MessageTags) {
clientOnlyTags = nil clientOnlyTags = nil
} }
msgid := server.generateMessageID()
// restrict messages appropriately when +R is set // restrict messages appropriately when +R is set
// intentionally make the sending user think the message went through fine // intentionally make the sending user think the message went through fine
allowedPlusR := !user.HasMode(modes.RegisteredOnly) || client.LoggedIntoAccount() allowedPlusR := !user.HasMode(modes.RegisteredOnly) || client.LoggedIntoAccount()
allowedTor := !user.isTor || !isRestrictedCTCPMessage(message) allowedTor := !user.isTor || !isRestrictedCTCPMessage(message)
if allowedPlusR && allowedTor { if allowedPlusR && allowedTor {
user.SendSplitMsgFromClient(msgid, client, clientOnlyTags, "PRIVMSG", user.nick, splitMsg) user.SendSplitMsgFromClient(client, clientOnlyTags, "PRIVMSG", user.nick, splitMsg)
} }
nickMaskString := client.NickMaskString() nickMaskString := client.NickMaskString()
accountName := client.AccountName() accountName := client.AccountName()
if client.capabilities.Has(caps.EchoMessage) { if client.capabilities.Has(caps.EchoMessage) {
rb.AddSplitMessageFromClient(msgid, nickMaskString, accountName, clientOnlyTags, "PRIVMSG", user.nick, splitMsg) rb.AddSplitMessageFromClient(nickMaskString, accountName, clientOnlyTags, "PRIVMSG", user.nick, splitMsg)
} }
if user.HasMode(modes.Away) { if user.HasMode(modes.Away) {
//TODO(dan): possibly implement cooldown of away notifications to users //TODO(dan): possibly implement cooldown of away notifications to users
@ -2171,7 +2165,6 @@ func privmsgHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *R
user.history.Add(history.Item{ user.history.Add(history.Item{
Type: history.Privmsg, Type: history.Privmsg,
Msgid: msgid,
Message: splitMsg, Message: splitMsg,
Nick: nickMaskString, Nick: nickMaskString,
AccountName: accountName, AccountName: accountName,
@ -2357,7 +2350,7 @@ func setnameHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *R
// TAGMSG <target>{,<target>} // TAGMSG <target>{,<target>}
func tagmsgHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool { func tagmsgHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool {
clientOnlyTags := utils.GetClientOnlyTags(msg.Tags) clientOnlyTags := msg.ClientOnlyTags()
// no client-only tags, so we can drop it // no client-only tags, so we can drop it
if clientOnlyTags == nil { if clientOnlyTags == nil {
return false return false
@ -2366,6 +2359,7 @@ func tagmsgHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Re
targets := strings.Split(msg.Params[0], ",") targets := strings.Split(msg.Params[0], ",")
cnick := client.Nick() cnick := client.Nick()
message := utils.MakeSplitMessage("", true) // assign consistent message ID
for i, targetString := range targets { for i, targetString := range targets {
// max of four targets per privmsg // max of four targets per privmsg
if i > maxTargets-1 { if i > maxTargets-1 {
@ -2390,9 +2384,7 @@ func tagmsgHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Re
rb.Add(nil, client.server.name, ERR_CANNOTSENDTOCHAN, channel.name, client.t("Cannot send to channel")) rb.Add(nil, client.server.name, ERR_CANNOTSENDTOCHAN, channel.name, client.t("Cannot send to channel"))
continue continue
} }
msgid := server.generateMessageID() channel.SendSplitMessage("TAGMSG", lowestPrefix, clientOnlyTags, client, message, rb)
channel.TagMsg(msgid, lowestPrefix, clientOnlyTags, client, rb)
} else { } else {
target, err = CasefoldName(targetString) target, err = CasefoldName(targetString)
user := server.clients.Get(target) user := server.clients.Get(target)
@ -2402,19 +2394,19 @@ func tagmsgHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Re
} }
continue continue
} }
msgid := server.generateMessageID()
// end user can't receive tagmsgs // end user can't receive tagmsgs
if !user.capabilities.Has(caps.MessageTags) { if !user.capabilities.Has(caps.MessageTags) {
continue continue
} }
user.SendFromClient(msgid, client, clientOnlyTags, "TAGMSG", user.nick) unick := user.Nick()
user.SendSplitMsgFromClient(client, clientOnlyTags, "TAGMSG", unick, message)
if client.capabilities.Has(caps.EchoMessage) { if client.capabilities.Has(caps.EchoMessage) {
rb.AddFromClient(msgid, client.NickMaskString(), client.AccountName(), clientOnlyTags, "TAGMSG", user.nick) rb.AddSplitMessageFromClient(client.NickMaskString(), client.AccountName(), clientOnlyTags, "TAGMSG", unick, message)
} }
if user.HasMode(modes.Away) { if user.HasMode(modes.Away) {
//TODO(dan): possibly implement cooldown of away notifications to users //TODO(dan): possibly implement cooldown of away notifications to users
rb.Add(nil, server.name, RPL_AWAY, cnick, user.Nick(), user.AwayMessage()) rb.Add(nil, server.name, RPL_AWAY, cnick, unick, user.AwayMessage())
} }
} }
} }

View File

@ -21,6 +21,7 @@ const (
Kick Kick
Quit Quit
Mode Mode
Tagmsg
) )
// Item represents an event (e.g., a PRIVMSG or a JOIN) and its associated data // Item represents an event (e.g., a PRIVMSG or a JOIN) and its associated data
@ -33,14 +34,13 @@ type Item struct {
AccountName string AccountName string
Message utils.SplitMessage Message utils.SplitMessage
// for non-privmsg items, we may stuff some other data in here // for non-privmsg items, we may stuff some other data in here
Msgid string
} }
// 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 {
// XXX we stuff other data in the Msgid field sometimes, // XXX we stuff other data in the Msgid field sometimes,
// don't match it by accident // don't match it by accident
return (item.Type == Privmsg || item.Type == Notice) && item.Msgid == msgid return (item.Type == Privmsg || item.Type == Notice) && item.Message.Msgid == msgid
} }
type Predicate func(item Item) (matches bool) type Predicate func(item Item) (matches bool)

View File

@ -53,14 +53,17 @@ type IdleTimer struct {
timer *time.Timer timer *time.Timer
} }
// NewIdleTimer sets up a new IdleTimer using constant timeouts. // Initialize sets up an IdleTimer and starts counting idle time;
func NewIdleTimer(client *Client) *IdleTimer { // if there is no activity from the client, it will eventually be stopped.
it := IdleTimer{ func (it *IdleTimer) Initialize(client *Client) {
registerTimeout: RegisterTimeout, it.client = client
client: client, it.registerTimeout = RegisterTimeout
}
it.idleTimeout, it.quitTimeout = it.recomputeDurations() it.idleTimeout, it.quitTimeout = it.recomputeDurations()
return &it
it.Lock()
defer it.Unlock()
it.state = TimerUnregistered
it.resetTimeout()
} }
// recomputeDurations recomputes the idle and quit durations, given the client's caps. // recomputeDurations recomputes the idle and quit durations, given the client's caps.
@ -81,15 +84,6 @@ func (it *IdleTimer) recomputeDurations() (idleTimeout, quitTimeout time.Duratio
return return
} }
// Start starts counting idle time; if there is no activity from the client,
// it will eventually be stopped.
func (it *IdleTimer) Start() {
it.Lock()
defer it.Unlock()
it.state = TimerUnregistered
it.resetTimeout()
}
func (it *IdleTimer) Touch() { func (it *IdleTimer) Touch() {
idleTimeout, quitTimeout := it.recomputeDurations() idleTimeout, quitTimeout := it.recomputeDurations()

View File

@ -175,7 +175,7 @@ func (lm *Manager) Translators() []string {
tlist = append(tlist, fmt.Sprintf("%s (%s): %s", info.Name, info.Code, info.Contributors)) tlist = append(tlist, fmt.Sprintf("%s (%s): %s", info.Name, info.Code, info.Contributors))
} }
sort.Sort(tlist) tlist.Sort()
return tlist return tlist
} }
@ -228,7 +228,7 @@ func (lm *Manager) Translate(languages []string, originalString string) string {
} }
func (lm *Manager) CapValue() string { func (lm *Manager) CapValue() string {
langCodes := make([]string, len(lm.Languages)+1) langCodes := make(sort.StringSlice, len(lm.Languages)+1)
langCodes[0] = strconv.Itoa(len(lm.Languages)) langCodes[0] = strconv.Itoa(len(lm.Languages))
i := 1 i := 1
for _, info := range lm.Languages { for _, info := range lm.Languages {
@ -239,5 +239,6 @@ func (lm *Manager) CapValue() string {
langCodes[i] = codeToken langCodes[i] = codeToken
i += 1 i += 1
} }
langCodes.Sort()
return strings.Join(langCodes, ",") return strings.Join(langCodes, ",")
} }

View File

@ -119,6 +119,7 @@ const (
ERR_NOTOPLEVEL = "413" ERR_NOTOPLEVEL = "413"
ERR_WILDTOPLEVEL = "414" ERR_WILDTOPLEVEL = "414"
ERR_BADMASK = "415" ERR_BADMASK = "415"
ERR_INPUTTOOLONG = "417"
ERR_UNKNOWNCOMMAND = "421" ERR_UNKNOWNCOMMAND = "421"
ERR_NOMOTD = "422" ERR_NOMOTD = "422"
ERR_NOADMININFO = "423" ERR_NOADMININFO = "423"

View File

@ -32,7 +32,8 @@ type ResponseBuffer struct {
// GetLabel returns the label from the given message. // GetLabel returns the label from the given message.
func GetLabel(msg ircmsg.IrcMessage) string { func GetLabel(msg ircmsg.IrcMessage) string {
return msg.Tags[caps.LabelTagName].Value _, value := msg.GetTag(caps.LabelTagName)
return value
} }
// NewResponseBuffer returns a new ResponseBuffer. // NewResponseBuffer returns a new ResponseBuffer.
@ -42,8 +43,7 @@ func NewResponseBuffer(target *Client) *ResponseBuffer {
} }
} }
// Add adds a standard new message to our queue. func (rb *ResponseBuffer) AddMessage(msg ircmsg.IrcMessage) {
func (rb *ResponseBuffer) Add(tags *map[string]ircmsg.TagValue, prefix string, command string, params ...string) {
if rb.finalized { if rb.finalized {
rb.target.server.logger.Error("internal", "message added to finalized ResponseBuffer, undefined behavior") rb.target.server.logger.Error("internal", "message added to finalized ResponseBuffer, undefined behavior")
debug.PrintStack() debug.PrintStack()
@ -52,33 +52,38 @@ func (rb *ResponseBuffer) Add(tags *map[string]ircmsg.TagValue, prefix string, c
return return
} }
message := ircmsg.MakeMessage(tags, prefix, command, params...) rb.messages = append(rb.messages, msg)
rb.messages = append(rb.messages, message) }
// Add adds a standard new message to our queue.
func (rb *ResponseBuffer) Add(tags map[string]string, prefix string, command string, params ...string) {
rb.AddMessage(ircmsg.MakeMessage(tags, prefix, command, params...))
} }
// AddFromClient adds a new message from a specific client to our queue. // AddFromClient adds a new message from a specific client to our queue.
func (rb *ResponseBuffer) AddFromClient(msgid string, fromNickMask string, fromAccount string, tags *map[string]ircmsg.TagValue, command string, params ...string) { func (rb *ResponseBuffer) AddFromClient(msgid string, fromNickMask string, fromAccount string, tags map[string]string, command string, params ...string) {
msg := ircmsg.MakeMessage(nil, fromNickMask, command, params...)
msg.UpdateTags(tags)
// attach account-tag // attach account-tag
if rb.target.capabilities.Has(caps.AccountTag) { if rb.target.capabilities.Has(caps.AccountTag) && fromAccount != "*" {
if fromAccount != "*" { msg.SetTag("account", fromAccount)
tags = ensureTag(tags, "account", fromAccount)
}
} }
// attach message-id // attach message-id
if len(msgid) > 0 && rb.target.capabilities.Has(caps.MessageTags) { if len(msgid) > 0 && rb.target.capabilities.Has(caps.MessageTags) {
tags = ensureTag(tags, "draft/msgid", msgid) msg.SetTag("draft/msgid", msgid)
} }
rb.Add(tags, fromNickMask, command, params...) rb.AddMessage(msg)
} }
// 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(msgid string, fromNickMask string, fromAccount string, tags *map[string]ircmsg.TagValue, 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 rb.target.capabilities.Has(caps.MaxLine) || message.Wrapped == nil { if rb.target.capabilities.Has(caps.MaxLine) || message.Wrapped == nil {
rb.AddFromClient(msgid, fromNickMask, fromAccount, tags, command, target, message.Original) rb.AddFromClient(message.Msgid, fromNickMask, fromAccount, tags, command, target, message.Message)
} else { } else {
for _, str := range message.Wrapped { for _, messagePair := range message.Wrapped {
rb.AddFromClient(msgid, fromNickMask, fromAccount, tags, command, target, str) rb.AddFromClient(messagePair.Msgid, fromNickMask, fromAccount, tags, command, target, messagePair.Message)
} }
} }
} }
@ -103,7 +108,7 @@ func (rb *ResponseBuffer) sendBatchStart(batchType string, blocking bool) {
message := ircmsg.MakeMessage(nil, rb.target.server.name, "BATCH", "+"+rb.batchID, batchType) message := ircmsg.MakeMessage(nil, rb.target.server.name, "BATCH", "+"+rb.batchID, batchType)
if rb.Label != "" { if rb.Label != "" {
message.Tags[caps.LabelTagName] = ircmsg.MakeTagValue(rb.Label) message.SetTag(caps.LabelTagName, rb.Label)
} }
rb.target.SendRawMessage(message, blocking) rb.target.SendRawMessage(message, blocking)
} }
@ -149,7 +154,7 @@ func (rb *ResponseBuffer) flushInternal(final bool, blocking bool) error {
// if label but no batch, add label to first message // if label but no batch, add label to first message
if useLabel && !useBatch && len(rb.messages) == 1 && rb.batchID == "" { if useLabel && !useBatch && len(rb.messages) == 1 && rb.batchID == "" {
rb.messages[0].Tags[caps.LabelTagName] = ircmsg.MakeTagValue(rb.Label) rb.messages[0].SetTag(caps.LabelTagName, rb.Label)
} else if useBatch { } else if useBatch {
rb.sendBatchStart(defaultBatchType, blocking) rb.sendBatchStart(defaultBatchType, blocking)
} }
@ -157,16 +162,13 @@ func (rb *ResponseBuffer) flushInternal(final bool, blocking bool) error {
// send each message out // send each message out
for _, message := range rb.messages { for _, message := range rb.messages {
// attach server-time if needed // attach server-time if needed
if rb.target.capabilities.Has(caps.ServerTime) { if rb.target.capabilities.Has(caps.ServerTime) && !message.HasTag("time") {
if !message.Tags["time"].HasValue { message.SetTag("time", time.Now().UTC().Format(IRCv3TimestampFormat))
t := time.Now().UTC().Format(IRCv3TimestampFormat)
message.Tags["time"] = ircmsg.MakeTagValue(t)
}
} }
// attach batch ID // attach batch ID
if rb.batchID != "" { if rb.batchID != "" {
message.Tags["batch"] = ircmsg.MakeTagValue(rb.batchID) message.SetTag("batch", rb.batchID)
} }
// send message out // send message out

View File

@ -387,11 +387,6 @@ func (server *Server) createListener(addr string, tlsConfig *tls.Config, isTor b
return &wrapper, nil return &wrapper, nil
} }
// generateMessageID returns a network-unique message ID.
func (server *Server) generateMessageID() string {
return utils.GenerateSecretToken()
}
// //
// server functionality // server functionality
// //
@ -623,7 +618,7 @@ func (server *Server) applyConfig(config *Config, initial bool) (err error) {
} else { } else {
// enforce configs that can't be changed after launch: // enforce configs that can't be changed after launch:
currentLimits := server.Limits() currentLimits := server.Limits()
if currentLimits.LineLen.Tags != config.Limits.LineLen.Tags || currentLimits.LineLen.Rest != config.Limits.LineLen.Rest { if currentLimits.LineLen.Rest != config.Limits.LineLen.Rest {
return fmt.Errorf("Maximum line length (linelen) cannot be changed after launching the server, rehash aborted") return fmt.Errorf("Maximum line length (linelen) cannot be changed after launching the server, rehash aborted")
} else if server.name != config.Server.Name { } 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")
@ -703,9 +698,9 @@ func (server *Server) applyConfig(config *Config, initial bool) (err error) {
} }
// MaxLine // MaxLine
if config.Limits.LineLen.Tags != 512 || config.Limits.LineLen.Rest != 512 { if config.Limits.LineLen.Rest != 512 {
SupportedCapabilities.Enable(caps.MaxLine) SupportedCapabilities.Enable(caps.MaxLine)
value := fmt.Sprintf("%d,%d", config.Limits.LineLen.Tags, config.Limits.LineLen.Rest) value := fmt.Sprintf("%d", config.Limits.LineLen.Rest)
CapValues.Set(caps.MaxLine, value) CapValues.Set(caps.MaxLine, value)
} }

View File

@ -20,6 +20,8 @@ import (
var ( var (
handshakeTimeout, _ = time.ParseDuration("5s") handshakeTimeout, _ = time.ParseDuration("5s")
errSendQExceeded = errors.New("SendQ exceeded") errSendQExceeded = errors.New("SendQ exceeded")
sendQExceededMessage = []byte("\r\nERROR :SendQ Exceeded\r\n")
) )
// Socket represents an IRC socket. // Socket represents an IRC socket.
@ -38,7 +40,7 @@ type Socket struct {
totalLength int totalLength int
closed bool closed bool
sendQExceeded bool sendQExceeded bool
finalData string // what to send when we die finalData []byte // what to send when we die
finalized bool finalized bool
} }
@ -196,7 +198,7 @@ func (socket *Socket) wakeWriter() {
} }
// SetFinalData sets the final data to send when the SocketWriter closes. // SetFinalData sets the final data to send when the SocketWriter closes.
func (socket *Socket) SetFinalData(data string) { func (socket *Socket) SetFinalData(data []byte) {
socket.Lock() socket.Lock()
defer socket.Unlock() defer socket.Unlock()
socket.finalData = data socket.finalData = data
@ -271,7 +273,7 @@ func (socket *Socket) finalize() {
socket.finalized = true socket.finalized = true
finalData := socket.finalData finalData := socket.finalData
if socket.sendQExceeded { if socket.sendQExceeded {
finalData = "\r\nERROR :SendQ Exceeded\r\n" finalData = sendQExceededMessage
} }
socket.Unlock() socket.Unlock()
@ -279,8 +281,8 @@ func (socket *Socket) finalize() {
return return
} }
if finalData != "" { if len(finalData) != 0 {
socket.conn.Write([]byte(finalData)) socket.conn.Write(finalData)
} }
// close the connection // close the connection

View File

@ -6,7 +6,6 @@
package irc package irc
import "github.com/oragono/oragono/irc/modes" import "github.com/oragono/oragono/irc/modes"
import "github.com/goshuirc/irc-go/ircmsg"
// ClientSet is a set of clients. // ClientSet is a set of clients.
type ClientSet map[*Client]bool type ClientSet map[*Client]bool
@ -57,5 +56,3 @@ func (members MemberSet) AnyHasMode(mode modes.Mode) bool {
// ChannelSet is a set of channels. // ChannelSet is a set of channels.
type ChannelSet map[*Channel]bool type ChannelSet map[*Channel]bool
type Tags *map[string]ircmsg.TagValue

View File

@ -1,27 +0,0 @@
// Copyright (c) 2016-2017 Daniel Oaks <daniel@danieloaks.net>
// released under the MIT license
package utils
import "github.com/goshuirc/irc-go/ircmsg"
// GetClientOnlyTags takes a tag map and returns a map containing just the client-only tags from it.
func GetClientOnlyTags(tags map[string]ircmsg.TagValue) *map[string]ircmsg.TagValue {
if len(tags) < 1 {
return nil
}
clientOnlyTags := make(map[string]ircmsg.TagValue)
for name, value := range tags {
if len(name) > 1 && name[0] == '+' {
clientOnlyTags[name] = value
}
}
if len(clientOnlyTags) < 1 {
return nil
}
return &clientOnlyTags
}

View File

@ -50,17 +50,32 @@ func WordWrap(text string, lineWidth int) []string {
return lines return lines
} }
// SplitMessage represents a message that's been split for sending. type MessagePair struct {
type SplitMessage struct { Message string
Original string Msgid string
Wrapped []string // if this is nil, Original didn't need wrapping and can be sent to anyone
} }
func MakeSplitMessage(original string, origIs512 bool) (result SplitMessage) { // SplitMessage represents a message that's been split for sending.
result.Original = original type SplitMessage struct {
MessagePair
Wrapped []MessagePair // if this is nil, `Message` didn't need wrapping and can be sent to anyone
}
if !origIs512 { const defaultLineWidth = 400
result.Wrapped = WordWrap(original, 400)
func MakeSplitMessage(original string, origIs512 bool) (result SplitMessage) {
result.Message = original
result.Msgid = GenerateSecretToken()
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

View File

@ -487,10 +487,8 @@ limits:
# maximum length of IRC lines # maximum length of IRC lines
# this should generally be 1024-2048, and will only apply when negotiated by clients # this should generally be 1024-2048, and will only apply when negotiated by clients
linelen: linelen:
# tags section # ratified version of the message-tags cap fixes the max tag length at 8191 bytes
tags: 2048 # configurable length for the rest of the message:
# rest of the message
rest: 2048 rest: 2048
# fakelag: prevents clients from spamming commands too rapidly # fakelag: prevents clients from spamming commands too rapidly

2
vendor

@ -1 +1 @@
Subproject commit 72043bab39044196e57bff1fe5e59c9ec81e59f3 Subproject commit 8ddbb531841add50f8b7aff8fe00bef311448aaa