3
0
mirror of https://github.com/ergochat/ergo.git synced 2024-12-22 10:42:52 +01:00

Merge pull request #442 from slingamn/message_tags.5

upgrade message-tags to non-draft version
This commit is contained in:
Daniel Oaks 2019-03-12 08:47:08 +10:00 committed by GitHub
commit baa7e5af0b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 200 additions and 313 deletions

4
Gopkg.lock generated
View File

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

View File

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

View File

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

View File

@ -14,7 +14,6 @@ import (
"sync"
"github.com/goshuirc/irc-go/ircmsg"
"github.com/oragono/oragono/irc/caps"
"github.com/oragono/oragono/irc/history"
"github.com/oragono/oragono/irc/modes"
@ -425,11 +424,13 @@ func (channel *Channel) Join(client *Client, key string, isSajoin bool, rb *Resp
channel.regenerateMembersCache()
message := utils.SplitMessage{}
message.Msgid = details.realname
channel.history.Add(history.Item{
Type: history.Join,
Nick: details.nickMask,
AccountName: details.accountName,
Msgid: details.realname,
Message: message,
})
return
@ -603,16 +604,17 @@ func (channel *Channel) replayHistoryItems(rb *ResponseBuffer, items []history.I
serverTime := client.capabilities.Has(caps.ServerTime)
for _, item := range items {
var tags Tags
var tags map[string]string
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 {
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:
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:
nick := stripMaskFromNick(item.Nick)
var message string
@ -624,16 +626,16 @@ func (channel *Channel) replayHistoryItems(rb *ResponseBuffer, items []history.I
rb.Add(tags, "HistServ", "PRIVMSG", chname, message)
case history.Part:
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)
case history.Quit:
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)
case history.Kick:
nick := stripMaskFromNick(item.Nick)
// 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)
}
}
@ -717,13 +719,20 @@ func (channel *Channel) CanSpeak(client *Client) bool {
return true
}
// TagMsg sends a tag message to everyone in this channel who can accept them.
func (channel *Channel) TagMsg(msgid string, minPrefix *modes.Mode, clientOnlyTags *map[string]ircmsg.TagValue, client *Client, rb *ResponseBuffer) {
channel.sendMessage(msgid, "TAGMSG", []caps.Capability{caps.MessageTags}, minPrefix, clientOnlyTags, client, nil, rb)
}
func (channel *Channel) SendSplitMessage(command string, minPrefix *modes.Mode, clientOnlyTags map[string]string, client *Client, message utils.SplitMessage, rb *ResponseBuffer) {
var histType history.ItemType
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) {
rb.Add(nil, client.server.name, ERR_CANNOTSENDTOCHAN, channel.name, client.t("Cannot send to channel"))
return
@ -736,85 +745,16 @@ func (channel *Channel) sendMessage(msgid, cmd string, requiredCaps []caps.Capab
}
// send echo-message
if client.capabilities.Has(caps.EchoMessage) {
var messageTagsToUse *map[string]ircmsg.TagValue
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
var tagsToUse map[string]string
if client.capabilities.Has(caps.MessageTags) {
tagsToUse = clientOnlyTags
}
nickMaskString := client.NickMaskString()
accountName := client.AccountName()
if message == nil {
rb.AddFromClient(msgid, nickMaskString, accountName, tagsToUse, cmd, channel.name)
if command == "TAGMSG" && client.capabilities.Has(caps.MessageTags) {
rb.AddFromClient(message.Msgid, nickMaskString, accountName, tagsToUse, command, channel.name)
} 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 {
continue
}
var tagsToUse *map[string]ircmsg.TagValue
var tagsToUse map[string]string
if member.capabilities.Has(caps.MessageTags) {
tagsToUse = clientOnlyTags
} else if command == "TAGMSG" {
continue
}
if message == nil {
member.sendFromClientInternal(false, now, msgid, nickmask, account, tagsToUse, cmd, channel.name)
if command == "TAGMSG" {
member.sendFromClientInternal(false, now, message.Msgid, nickmask, account, tagsToUse, command, channel.name)
} 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{
Type: histType,
Msgid: msgid,
Message: *message,
Message: message,
Nick: nickmask,
AccountName: account,
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)
}
message := utils.SplitMessage{}
message.Message = comment
message.Msgid = targetNick // XXX abuse this field
channel.history.Add(history.Item{
Type: history.Kick,
Nick: clientMask,
Message: utils.MakeSplitMessage(comment, true),
AccountName: target.AccountName(),
Msgid: targetNick, // XXX abuse this field
Message: message,
})
channel.Quit(target)

View File

@ -62,14 +62,13 @@ type Client struct {
hasQuit bool
hops int
hostname string
idletimer *IdleTimer
idletimer IdleTimer
invitedTo map[string]bool
isDestroyed bool
isTor bool
isQuitting bool
languages []string
loginThrottle connection_limits.GenericThrottle
maxlenTags uint32
maxlenRest uint32
nick string
nickCasefolded string
@ -122,8 +121,9 @@ type ClientDetails struct {
func RunNewClient(server *Server, conn clientConn) {
now := time.Now()
config := server.Config()
fullLineLenLimit := config.Limits.LineLen.Tags + config.Limits.LineLen.Rest
socket := NewSocket(conn.Conn, fullLineLenLimit*2, config.Server.MaxSendQBytes)
fullLineLenLimit := ircmsg.MaxlenTagsFromClient + config.Limits.LineLen.Rest
// give them 1k of grace over the limit:
socket := NewSocket(conn.Conn, fullLineLenLimit+1024, config.Server.MaxSendQBytes)
client := &Client{
atime: now,
capabilities: caps.NewSet(),
@ -246,30 +246,21 @@ func (client *Client) IPString() string {
// command goroutine
//
func (client *Client) recomputeMaxlens() (int, int) {
maxlenTags := 512
func (client *Client) recomputeMaxlens() int {
maxlenRest := 512
if client.capabilities.Has(caps.MessageTags) {
maxlenTags = 4096
}
if client.capabilities.Has(caps.MaxLine) {
limits := client.server.Limits()
if limits.LineLen.Tags > maxlenTags {
maxlenTags = limits.LineLen.Tags
}
maxlenRest = limits.LineLen.Rest
maxlenRest = client.server.Limits().LineLen.Rest
}
atomic.StoreUint32(&client.maxlenTags, uint32(maxlenTags))
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
// so that Client.Send doesn't have to acquire any Client locks
func (client *Client) maxlens() (int, int) {
return int(atomic.LoadUint32(&client.maxlenTags)), int(atomic.LoadUint32(&client.maxlenRest))
func (client *Client) MaxlenRest() int {
return int(atomic.LoadUint32(&client.maxlenRest))
}
func (client *Client) run() {
@ -292,8 +283,7 @@ func (client *Client) run() {
client.destroy(false)
}()
client.idletimer = NewIdleTimer(client)
client.idletimer.Start()
client.idletimer.Initialize(client)
client.nickTimer.Initialize(client)
@ -302,7 +292,7 @@ func (client *Client) run() {
firstLine := true
for {
maxlenTags, maxlenRest := client.recomputeMaxlens()
maxlenRest := client.recomputeMaxlens()
line, err = client.socket.Read()
if err != nil {
@ -331,9 +321,12 @@ func (client *Client) run() {
}
}
msg, err = ircmsg.ParseLineMaxLen(line, maxlenTags, maxlenRest)
msg, err = ircmsg.ParseLineStrict(line, true, maxlenRest)
if err == ircmsg.ErrorLineIsEmpty {
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 {
client.Quit(client.t("Received malformed line"))
break
@ -539,11 +532,11 @@ func (client *Client) replayPrivmsgHistory(rb *ResponseBuffer, items []history.I
default:
continue
}
var tags Tags
var tags map[string]string
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 {
rb.Add(nil, "HistServ", "NOTICE", nick, client.t("Some additional message history may have been lost"))
@ -841,17 +834,18 @@ func (client *Client) Quit(message string) {
return
}
var quitLine string
var finalData []byte
// #364: don't send QUIT lines to unregistered clients
if registered {
quitMsg := ircmsg.MakeMessage(nil, prefix, "QUIT", message)
quitLine, _ = quitMsg.Line()
finalData, _ = quitMsg.LineBytesStrict(false, 512)
}
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.
@ -962,50 +956,45 @@ func (client *Client) destroy(beingResumed bool) {
// SendSplitMsgFromClient sends an IRC PRIVMSG/NOTICE coming from a specific client.
// Adds account-tag to the line as well.
func (client *Client) SendSplitMsgFromClient(msgid string, from *Client, tags Tags, command, target string, message utils.SplitMessage) {
client.sendSplitMsgFromClientInternal(false, time.Time{}, msgid, from.NickMaskString(), from.AccountName(), tags, command, target, message)
func (client *Client) SendSplitMsgFromClient(from *Client, tags map[string]string, command, target string, message utils.SplitMessage) {
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 {
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 {
for _, str := range message.Wrapped {
client.sendFromClientInternal(blocking, serverTime, msgid, nickmask, accountName, tags, command, target, str)
for _, messagePair := range message.Wrapped {
client.sendFromClientInternal(blocking, serverTime, messagePair.Msgid, nickmask, accountName, tags, command, target, messagePair.Message)
}
}
}
// SendFromClient sends an IRC line coming from a specific client.
// 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...)
}
// helper to add a tag to `tags` (or create a new tag set if the current one is nil)
func ensureTag(tags Tags, tagName, tagValue string) (result Tags) {
if tags == nil {
result = ircmsg.MakeTags(tagName, tagValue)
} else {
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 {
// this is SendFromClient, but directly exposing nickmask and accountName,
// for things like history replay and CHGHOST where they no longer (necessarily)
// correspond to the current state of a client
func (client *Client) sendFromClientInternal(blocking bool, serverTime time.Time, msgid string, nickmask, accountName string, tags map[string]string, command string, params ...string) error {
msg := ircmsg.MakeMessage(tags, nickmask, command, params...)
// attach account-tag
if client.capabilities.Has(caps.AccountTag) && accountName != "*" {
tags = ensureTag(tags, "account", accountName)
msg.SetTag("account", accountName)
}
// attach message-id
if len(msgid) > 0 && client.capabilities.Has(caps.MessageTags) {
tags = ensureTag(tags, "draft/msgid", msgid)
if msgid != "" && client.capabilities.Has(caps.MessageTags) {
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 (
@ -1025,24 +1014,24 @@ var (
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
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]
// 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 + " "
usedTrailingHack = true
}
}
// assemble message
maxlenTags, maxlenRest := client.maxlens()
line, err := message.LineMaxLenBytes(maxlenTags, maxlenRest)
maxlenRest := client.MaxlenRest()
line, err := message.LineBytesStrict(false, maxlenRest)
if err != nil {
logline := fmt.Sprintf("Error assembling message for sending: %v\n%s", err, debug.Stack())
client.server.logger.Error("internal", logline)
message = ircmsg.MakeMessage(nil, client.server.name, ERR_UNKNOWNERROR, "*", "Error assembling message for sending")
line, _ := message.LineBytes()
line, _ := message.LineBytesStrict(false, 0)
if blocking {
client.socket.BlockingWrite(line)
@ -1054,7 +1043,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 usedTrailingHack {
copy(line[len(line)-3:], []byte{'\r', '\n'})
copy(line[len(line)-3:], "\r\n")
line = line[:len(line)-1]
}
@ -1070,24 +1059,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.
func (client *Client) Send(tags Tags, prefix string, command string, params ...string) error {
return client.sendInternal(false, time.Time{}, tags, prefix, command, params...)
func (client *Client) Send(tags map[string]string, prefix string, command string, params ...string) error {
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.

View File

@ -202,7 +202,6 @@ type OperConfig struct {
// LineLenConfig controls line lengths.
type LineLenLimits struct {
Tags int
Rest int
}
@ -553,9 +552,6 @@ func LoadConfig(filename string) (config *Config, err error) {
}
config.Server.WebIRC = newWebIRC
// process limits
if config.Limits.LineLen.Tags < 512 {
config.Limits.LineLen.Tags = 512
}
if 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>
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], ",")
message := msg.Params[1]
@ -1883,7 +1883,6 @@ func noticeHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Re
return false
}
// split privmsg
splitMsg := utils.MakeSplitMessage(message, !client.capabilities.Has(caps.MaxLine))
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
continue
}
msgid := server.generateMessageID()
channel.SplitNotice(msgid, lowestPrefix, clientOnlyTags, client, splitMsg, rb)
channel.SendSplitMessage("NOTICE", lowestPrefix, clientOnlyTags, client, splitMsg, rb)
} else {
target, err := CasefoldName(targetString)
if err != nil {
@ -1926,23 +1924,21 @@ func noticeHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Re
if !user.capabilities.Has(caps.MessageTags) {
clientOnlyTags = nil
}
msgid := server.generateMessageID()
// restrict messages appropriately when +R is set
// intentionally make the sending user think the message went through fine
allowedPlusR := !user.HasMode(modes.RegisteredOnly) || client.LoggedIntoAccount()
allowedTor := !user.isTor || !isRestrictedCTCPMessage(message)
if allowedPlusR && allowedTor {
user.SendSplitMsgFromClient(msgid, client, clientOnlyTags, "NOTICE", user.nick, splitMsg)
user.SendSplitMsgFromClient(client, clientOnlyTags, "NOTICE", user.nick, splitMsg)
}
nickMaskString := client.NickMaskString()
accountName := client.AccountName()
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{
Type: history.Notice,
Msgid: msgid,
Message: splitMsg,
Nick: nickMaskString,
AccountName: accountName,
@ -2096,7 +2092,7 @@ func isRestrictedCTCPMessage(message string) bool {
// PRIVMSG <target>{,<target>} <message>
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], ",")
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"))
continue
}
msgid := server.generateMessageID()
channel.SplitPrivMsg(msgid, lowestPrefix, clientOnlyTags, client, splitMsg, rb)
channel.SendSplitMessage("PRIVMSG", lowestPrefix, clientOnlyTags, client, splitMsg, rb)
} else {
target, err = CasefoldName(targetString)
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) {
clientOnlyTags = nil
}
msgid := server.generateMessageID()
// restrict messages appropriately when +R is set
// intentionally make the sending user think the message went through fine
allowedPlusR := !user.HasMode(modes.RegisteredOnly) || client.LoggedIntoAccount()
allowedTor := !user.isTor || !isRestrictedCTCPMessage(message)
if allowedPlusR && allowedTor {
user.SendSplitMsgFromClient(msgid, client, clientOnlyTags, "PRIVMSG", user.nick, splitMsg)
user.SendSplitMsgFromClient(client, clientOnlyTags, "PRIVMSG", user.nick, splitMsg)
}
nickMaskString := client.NickMaskString()
accountName := client.AccountName()
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) {
//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{
Type: history.Privmsg,
Msgid: msgid,
Message: splitMsg,
Nick: nickMaskString,
AccountName: accountName,
@ -2353,7 +2346,7 @@ func setnameHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *R
// TAGMSG <target>{,<target>}
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
if clientOnlyTags == nil {
return false
@ -2362,6 +2355,7 @@ func tagmsgHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Re
targets := strings.Split(msg.Params[0], ",")
cnick := client.Nick()
message := utils.MakeSplitMessage("", true) // assign consistent message ID
for i, targetString := range targets {
// max of four targets per privmsg
if i > maxTargets-1 {
@ -2386,9 +2380,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"))
continue
}
msgid := server.generateMessageID()
channel.TagMsg(msgid, lowestPrefix, clientOnlyTags, client, rb)
channel.SendSplitMessage("TAGMSG", lowestPrefix, clientOnlyTags, client, message, rb)
} else {
target, err = CasefoldName(targetString)
user := server.clients.Get(target)
@ -2398,19 +2390,19 @@ func tagmsgHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Re
}
continue
}
msgid := server.generateMessageID()
// end user can't receive tagmsgs
if !user.capabilities.Has(caps.MessageTags) {
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) {
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) {
//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
Quit
Mode
Tagmsg
)
// Item represents an event (e.g., a PRIVMSG or a JOIN) and its associated data
@ -33,14 +34,13 @@ type Item struct {
AccountName string
Message utils.SplitMessage
// for non-privmsg items, we may stuff some other data in here
Msgid string
}
// HasMsgid tests whether a message has the message id `msgid`.
func (item *Item) HasMsgid(msgid string) bool {
// XXX we stuff other data in the Msgid field sometimes,
// 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)

View File

@ -54,14 +54,17 @@ type IdleTimer struct {
timer *time.Timer
}
// NewIdleTimer sets up a new IdleTimer using constant timeouts.
func NewIdleTimer(client *Client) *IdleTimer {
it := IdleTimer{
registerTimeout: RegisterTimeout,
client: client,
}
// Initialize sets up an IdleTimer and starts counting idle time;
// if there is no activity from the client, it will eventually be stopped.
func (it *IdleTimer) Initialize(client *Client) {
it.client = client
it.registerTimeout = RegisterTimeout
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.
@ -82,15 +85,6 @@ func (it *IdleTimer) recomputeDurations() (idleTimeout, quitTimeout time.Duratio
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() {
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))
}
sort.Sort(tlist)
tlist.Sort()
return tlist
}
@ -228,7 +228,7 @@ func (lm *Manager) Translate(languages []string, originalString string) 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))
i := 1
for _, info := range lm.Languages {
@ -239,5 +239,6 @@ func (lm *Manager) CapValue() string {
langCodes[i] = codeToken
i += 1
}
langCodes.Sort()
return strings.Join(langCodes, ",")
}

View File

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

View File

@ -32,7 +32,8 @@ type ResponseBuffer struct {
// GetLabel returns the label from the given message.
func GetLabel(msg ircmsg.IrcMessage) string {
return msg.Tags[caps.LabelTagName].Value
_, value := msg.GetTag(caps.LabelTagName)
return value
}
// 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) Add(tags *map[string]ircmsg.TagValue, prefix string, command string, params ...string) {
func (rb *ResponseBuffer) AddMessage(msg ircmsg.IrcMessage) {
if rb.finalized {
rb.target.server.logger.Error("internal", "message added to finalized ResponseBuffer, undefined behavior")
debug.PrintStack()
@ -52,33 +52,38 @@ func (rb *ResponseBuffer) Add(tags *map[string]ircmsg.TagValue, prefix string, c
return
}
message := ircmsg.MakeMessage(tags, prefix, command, params...)
rb.messages = append(rb.messages, message)
rb.messages = append(rb.messages, msg)
}
// 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.
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
if rb.target.capabilities.Has(caps.AccountTag) {
if fromAccount != "*" {
tags = ensureTag(tags, "account", fromAccount)
}
if rb.target.capabilities.Has(caps.AccountTag) && fromAccount != "*" {
msg.SetTag("account", fromAccount)
}
// attach message-id
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.
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 {
rb.AddFromClient(msgid, fromNickMask, fromAccount, tags, command, target, message.Original)
rb.AddFromClient(message.Msgid, fromNickMask, fromAccount, tags, command, target, message.Message)
} else {
for _, str := range message.Wrapped {
rb.AddFromClient(msgid, fromNickMask, fromAccount, tags, command, target, str)
for _, messagePair := range message.Wrapped {
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)
if rb.Label != "" {
message.Tags[caps.LabelTagName] = ircmsg.MakeTagValue(rb.Label)
message.SetTag(caps.LabelTagName, rb.Label)
}
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 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 {
rb.sendBatchStart(defaultBatchType, blocking)
}
@ -157,16 +162,13 @@ func (rb *ResponseBuffer) flushInternal(final bool, blocking bool) error {
// send each message out
for _, message := range rb.messages {
// attach server-time if needed
if rb.target.capabilities.Has(caps.ServerTime) {
if !message.Tags["time"].HasValue {
t := time.Now().UTC().Format(IRCv3TimestampFormat)
message.Tags["time"] = ircmsg.MakeTagValue(t)
}
if rb.target.capabilities.Has(caps.ServerTime) && !message.HasTag("time") {
message.SetTag("time", time.Now().UTC().Format(IRCv3TimestampFormat))
}
// attach batch ID
if rb.batchID != "" {
message.Tags["batch"] = ircmsg.MakeTagValue(rb.batchID)
message.SetTag("batch", rb.batchID)
}
// send message out

View File

@ -387,11 +387,6 @@ func (server *Server) createListener(addr string, tlsConfig *tls.Config, isTor b
return &wrapper, nil
}
// generateMessageID returns a network-unique message ID.
func (server *Server) generateMessageID() string {
return utils.GenerateSecretToken()
}
//
// server functionality
//
@ -623,7 +618,7 @@ func (server *Server) applyConfig(config *Config, initial bool) (err error) {
} else {
// enforce configs that can't be changed after launch:
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")
} else if server.name != config.Server.Name {
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
if config.Limits.LineLen.Tags != 512 || config.Limits.LineLen.Rest != 512 {
if config.Limits.LineLen.Rest != 512 {
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)
}

View File

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

View File

@ -6,7 +6,6 @@
package irc
import "github.com/oragono/oragono/irc/modes"
import "github.com/goshuirc/irc-go/ircmsg"
// ClientSet is a set of clients.
type ClientSet map[*Client]bool
@ -57,5 +56,3 @@ func (members MemberSet) AnyHasMode(mode modes.Mode) bool {
// ChannelSet is a set of channels.
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
}
// SplitMessage represents a message that's been split for sending.
type SplitMessage struct {
Original string
Wrapped []string // if this is nil, Original didn't need wrapping and can be sent to anyone
type MessagePair struct {
Message string
Msgid string
}
func MakeSplitMessage(original string, origIs512 bool) (result SplitMessage) {
result.Original = original
// SplitMessage represents a message that's been split for sending.
type SplitMessage struct {
MessagePair
Wrapped []MessagePair // if this is nil, `Message` didn't need wrapping and can be sent to anyone
}
if !origIs512 {
result.Wrapped = WordWrap(original, 400)
const defaultLineWidth = 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

View File

@ -487,10 +487,8 @@ limits:
# maximum length of IRC lines
# this should generally be 1024-2048, and will only apply when negotiated by clients
linelen:
# tags section
tags: 2048
# rest of the message
# 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
# fakelag: prevents clients from spamming commands too rapidly

2
vendor

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