Browse Source

Merge pull request #442 from slingamn/message_tags.5

upgrade message-tags to non-draft version
tags/v1.1.0-rc1
Daniel Oaks 5 years ago
parent
commit
baa7e5af0b
No account linked to committer's email address
19 changed files with 195 additions and 308 deletions
  1. 2
    2
      Gopkg.lock
  2. 5
    5
      gencapdefs.py
  3. 6
    6
      irc/caps/defs.go
  4. 37
    94
      irc/channel.go
  5. 55
    77
      irc/client.go
  6. 0
    4
      irc/config.go
  7. 15
    23
      irc/handlers.go
  8. 2
    2
      irc/history/history.go
  9. 10
    16
      irc/idletimer.go
  10. 3
    2
      irc/languages/languages.go
  11. 1
    0
      irc/numerics.go
  12. 26
    24
      irc/responsebuffer.go
  13. 3
    8
      irc/server.go
  14. 7
    5
      irc/socket.go
  15. 0
    3
      irc/types.go
  16. 0
    27
      irc/utils/message_tags.go
  17. 20
    5
      irc/utils/text.go
  18. 2
    4
      oragono.yaml
  19. 1
    1
      vendor

+ 2
- 2
Gopkg.lock View File

27
 
27
 
28
 [[projects]]
28
 [[projects]]
29
   branch = "master"
29
   branch = "master"
30
-  digest = "1:6bcd7bcd5e14cc9552fbf83b2f77f24935c0c502d009c9825b6c212c3f8eb967"
30
+  digest = "1:e6ed6eaa63211bb90847d8c5f11d7412e56c96b5befb7402ee7a7a8ad02700ec"
31
   name = "github.com/goshuirc/irc-go"
31
   name = "github.com/goshuirc/irc-go"
32
   packages = [
32
   packages = [
33
     "ircfmt",
33
     "ircfmt",
35
     "ircmsg",
35
     "ircmsg",
36
   ]
36
   ]
37
   pruneopts = "UT"
37
   pruneopts = "UT"
38
-  revision = "cf199aea7186fd960d0ed5abbf579bb0f9d890d1"
38
+  revision = "ca74bf6a176d2d1dce6f28f99901a2d48d8da2bd"
39
 
39
 
40
 [[projects]]
40
 [[projects]]
41
   digest = "1:c658e84ad3916da105a761660dcaeb01e63416c8ec7bc62256a9b411a05fcd67"
41
   digest = "1:c658e84ad3916da105a761660dcaeb01e63416c8ec7bc62256a9b411a05fcd67"

+ 5
- 5
gencapdefs.py View File

83
     ),
83
     ),
84
     CapDef(
84
     CapDef(
85
         identifier="MaxLine",
85
         identifier="MaxLine",
86
-        name="oragono.io/maxline",
87
-        url="https://oragono.io/maxline",
86
+        name="oragono.io/maxline-2",
87
+        url="https://oragono.io/maxline-2",
88
         standard="Oragono-specific",
88
         standard="Oragono-specific",
89
     ),
89
     ),
90
     CapDef(
90
     CapDef(
91
         identifier="MessageTags",
91
         identifier="MessageTags",
92
-        name="draft/message-tags-0.2",
93
-        url="https://ircv3.net/specs/core/message-tags-3.3.html",
94
-        standard="draft IRCv3",
92
+        name="message-tags",
93
+        url="https://ircv3.net/specs/extensions/message-tags.html",
94
+        standard="IRCv3",
95
     ),
95
     ),
96
     CapDef(
96
     CapDef(
97
         identifier="MultiPrefix",
97
         identifier="MultiPrefix",

+ 6
- 6
irc/caps/defs.go View File

57
 	// https://gist.github.com/DanielOaks/8126122f74b26012a3de37db80e4e0c6
57
 	// https://gist.github.com/DanielOaks/8126122f74b26012a3de37db80e4e0c6
58
 	Languages Capability = iota
58
 	Languages Capability = iota
59
 
59
 
60
-	// MaxLine is the Oragono-specific capability named "oragono.io/maxline":
61
-	// https://oragono.io/maxline
60
+	// MaxLine is the Oragono-specific capability named "oragono.io/maxline-2":
61
+	// https://oragono.io/maxline-2
62
 	MaxLine Capability = iota
62
 	MaxLine Capability = iota
63
 
63
 
64
-	// MessageTags is the draft IRCv3 capability named "draft/message-tags-0.2":
65
-	// https://ircv3.net/specs/core/message-tags-3.3.html
64
+	// MessageTags is the IRCv3 capability named "message-tags":
65
+	// https://ircv3.net/specs/extensions/message-tags.html
66
 	MessageTags Capability = iota
66
 	MessageTags Capability = iota
67
 
67
 
68
 	// MultiPrefix is the IRCv3 capability named "multi-prefix":
68
 	// MultiPrefix is the IRCv3 capability named "multi-prefix":
112
 		"invite-notify",
112
 		"invite-notify",
113
 		"draft/labeled-response",
113
 		"draft/labeled-response",
114
 		"draft/languages",
114
 		"draft/languages",
115
-		"oragono.io/maxline",
116
-		"draft/message-tags-0.2",
115
+		"oragono.io/maxline-2",
116
+		"message-tags",
117
 		"multi-prefix",
117
 		"multi-prefix",
118
 		"draft/rename",
118
 		"draft/rename",
119
 		"draft/resume-0.3",
119
 		"draft/resume-0.3",

+ 37
- 94
irc/channel.go View File

14
 
14
 
15
 	"sync"
15
 	"sync"
16
 
16
 
17
-	"github.com/goshuirc/irc-go/ircmsg"
18
 	"github.com/oragono/oragono/irc/caps"
17
 	"github.com/oragono/oragono/irc/caps"
19
 	"github.com/oragono/oragono/irc/history"
18
 	"github.com/oragono/oragono/irc/history"
20
 	"github.com/oragono/oragono/irc/modes"
19
 	"github.com/oragono/oragono/irc/modes"
425
 
424
 
426
 		channel.regenerateMembersCache()
425
 		channel.regenerateMembersCache()
427
 
426
 
427
+		message := utils.SplitMessage{}
428
+		message.Msgid = details.realname
428
 		channel.history.Add(history.Item{
429
 		channel.history.Add(history.Item{
429
 			Type:        history.Join,
430
 			Type:        history.Join,
430
 			Nick:        details.nickMask,
431
 			Nick:        details.nickMask,
431
 			AccountName: details.accountName,
432
 			AccountName: details.accountName,
432
-			Msgid:       details.realname,
433
+			Message:     message,
433
 		})
434
 		})
434
 
435
 
435
 		return
436
 		return
603
 	serverTime := client.capabilities.Has(caps.ServerTime)
604
 	serverTime := client.capabilities.Has(caps.ServerTime)
604
 
605
 
605
 	for _, item := range items {
606
 	for _, item := range items {
606
-		var tags Tags
607
+		var tags map[string]string
607
 		if serverTime {
608
 		if serverTime {
608
-			tags = ensureTag(tags, "time", item.Time.Format(IRCv3TimestampFormat))
609
+			tags = map[string]string{"time": item.Time.Format(IRCv3TimestampFormat)}
609
 		}
610
 		}
610
 
611
 
612
+		// TODO(#437) support history.Tagmsg
611
 		switch item.Type {
613
 		switch item.Type {
612
 		case history.Privmsg:
614
 		case history.Privmsg:
613
-			rb.AddSplitMessageFromClient(item.Msgid, item.Nick, item.AccountName, tags, "PRIVMSG", chname, item.Message)
615
+			rb.AddSplitMessageFromClient(item.Nick, item.AccountName, tags, "PRIVMSG", chname, item.Message)
614
 		case history.Notice:
616
 		case history.Notice:
615
-			rb.AddSplitMessageFromClient(item.Msgid, item.Nick, item.AccountName, tags, "NOTICE", chname, item.Message)
617
+			rb.AddSplitMessageFromClient(item.Nick, item.AccountName, tags, "NOTICE", chname, item.Message)
616
 		case history.Join:
618
 		case history.Join:
617
 			nick := stripMaskFromNick(item.Nick)
619
 			nick := stripMaskFromNick(item.Nick)
618
 			var message string
620
 			var message string
624
 			rb.Add(tags, "HistServ", "PRIVMSG", chname, message)
626
 			rb.Add(tags, "HistServ", "PRIVMSG", chname, message)
625
 		case history.Part:
627
 		case history.Part:
626
 			nick := stripMaskFromNick(item.Nick)
628
 			nick := stripMaskFromNick(item.Nick)
627
-			message := fmt.Sprintf(client.t("%[1]s left the channel (%[2]s)"), nick, item.Message.Original)
629
+			message := fmt.Sprintf(client.t("%[1]s left the channel (%[2]s)"), nick, item.Message.Message)
628
 			rb.Add(tags, "HistServ", "PRIVMSG", chname, message)
630
 			rb.Add(tags, "HistServ", "PRIVMSG", chname, message)
629
 		case history.Quit:
631
 		case history.Quit:
630
 			nick := stripMaskFromNick(item.Nick)
632
 			nick := stripMaskFromNick(item.Nick)
631
-			message := fmt.Sprintf(client.t("%[1]s quit (%[2]s)"), nick, item.Message.Original)
633
+			message := fmt.Sprintf(client.t("%[1]s quit (%[2]s)"), nick, item.Message.Message)
632
 			rb.Add(tags, "HistServ", "PRIVMSG", chname, message)
634
 			rb.Add(tags, "HistServ", "PRIVMSG", chname, message)
633
 		case history.Kick:
635
 		case history.Kick:
634
 			nick := stripMaskFromNick(item.Nick)
636
 			nick := stripMaskFromNick(item.Nick)
635
 			// XXX Msgid is the kick target
637
 			// XXX Msgid is the kick target
636
-			message := fmt.Sprintf(client.t("%[1]s kicked %[2]s (%[3]s)"), nick, item.Msgid, item.Message.Original)
638
+			message := fmt.Sprintf(client.t("%[1]s kicked %[2]s (%[3]s)"), nick, item.Message.Msgid, item.Message.Message)
637
 			rb.Add(tags, "HistServ", "PRIVMSG", chname, message)
639
 			rb.Add(tags, "HistServ", "PRIVMSG", chname, message)
638
 		}
640
 		}
639
 	}
641
 	}
717
 	return true
719
 	return true
718
 }
720
 }
719
 
721
 
720
-// TagMsg sends a tag message to everyone in this channel who can accept them.
721
-func (channel *Channel) TagMsg(msgid string, minPrefix *modes.Mode, clientOnlyTags *map[string]ircmsg.TagValue, client *Client, rb *ResponseBuffer) {
722
-	channel.sendMessage(msgid, "TAGMSG", []caps.Capability{caps.MessageTags}, minPrefix, clientOnlyTags, client, nil, rb)
723
-}
724
-
725
-// sendMessage sends a given message to everyone on this channel.
726
-func (channel *Channel) sendMessage(msgid, cmd string, requiredCaps []caps.Capability, minPrefix *modes.Mode, clientOnlyTags *map[string]ircmsg.TagValue, client *Client, message *string, rb *ResponseBuffer) {
727
-	if !channel.CanSpeak(client) {
728
-		rb.Add(nil, client.server.name, ERR_CANNOTSENDTOCHAN, channel.name, client.t("Cannot send to channel"))
722
+func (channel *Channel) SendSplitMessage(command string, minPrefix *modes.Mode, clientOnlyTags map[string]string, client *Client, message utils.SplitMessage, rb *ResponseBuffer) {
723
+	var histType history.ItemType
724
+	switch command {
725
+	case "PRIVMSG":
726
+		histType = history.Privmsg
727
+	case "NOTICE":
728
+		histType = history.Notice
729
+	case "TAGMSG":
730
+		histType = history.Tagmsg
731
+	default:
732
+		channel.server.logger.Error("internal", "unrecognized Channel.SendSplitMessage command", command)
729
 		return
733
 		return
730
 	}
734
 	}
731
 
735
 
732
-	// for STATUSMSG
733
-	var minPrefixMode modes.Mode
734
-	if minPrefix != nil {
735
-		minPrefixMode = *minPrefix
736
-	}
737
-	// send echo-message
738
-	if client.capabilities.Has(caps.EchoMessage) {
739
-		var messageTagsToUse *map[string]ircmsg.TagValue
740
-		if client.capabilities.Has(caps.MessageTags) {
741
-			messageTagsToUse = clientOnlyTags
742
-		}
743
-
744
-		nickMaskString := client.NickMaskString()
745
-		accountName := client.AccountName()
746
-		if message == nil {
747
-			rb.AddFromClient(msgid, nickMaskString, accountName, messageTagsToUse, cmd, channel.name)
748
-		} else {
749
-			rb.AddFromClient(msgid, nickMaskString, accountName, messageTagsToUse, cmd, channel.name, *message)
750
-		}
751
-	}
752
-	for _, member := range channel.Members() {
753
-		if minPrefix != nil && !channel.ClientIsAtLeast(member, minPrefixMode) {
754
-			// STATUSMSG
755
-			continue
756
-		}
757
-		// echo-message is handled above, so skip sending the msg to the user themselves as well
758
-		if member == client {
759
-			continue
760
-		}
761
-
762
-		canReceive := true
763
-		for _, capName := range requiredCaps {
764
-			if !member.capabilities.Has(capName) {
765
-				canReceive = false
766
-			}
767
-		}
768
-		if !canReceive {
769
-			continue
770
-		}
771
-
772
-		var messageTagsToUse *map[string]ircmsg.TagValue
773
-		if member.capabilities.Has(caps.MessageTags) {
774
-			messageTagsToUse = clientOnlyTags
775
-		}
776
-
777
-		if message == nil {
778
-			member.SendFromClient(msgid, client, messageTagsToUse, cmd, channel.name)
779
-		} else {
780
-			member.SendFromClient(msgid, client, messageTagsToUse, cmd, channel.name, *message)
781
-		}
782
-	}
783
-}
784
-
785
-// SplitPrivMsg sends a private message to everyone in this channel.
786
-func (channel *Channel) SplitPrivMsg(msgid string, minPrefix *modes.Mode, clientOnlyTags *map[string]ircmsg.TagValue, client *Client, message utils.SplitMessage, rb *ResponseBuffer) {
787
-	channel.sendSplitMessage(msgid, "PRIVMSG", history.Privmsg, minPrefix, clientOnlyTags, client, &message, rb)
788
-}
789
-
790
-// SplitNotice sends a private message to everyone in this channel.
791
-func (channel *Channel) SplitNotice(msgid string, minPrefix *modes.Mode, clientOnlyTags *map[string]ircmsg.TagValue, client *Client, message utils.SplitMessage, rb *ResponseBuffer) {
792
-	channel.sendSplitMessage(msgid, "NOTICE", history.Notice, minPrefix, clientOnlyTags, client, &message, rb)
793
-}
794
-
795
-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) {
796
 	if !channel.CanSpeak(client) {
736
 	if !channel.CanSpeak(client) {
797
 		rb.Add(nil, client.server.name, ERR_CANNOTSENDTOCHAN, channel.name, client.t("Cannot send to channel"))
737
 		rb.Add(nil, client.server.name, ERR_CANNOTSENDTOCHAN, channel.name, client.t("Cannot send to channel"))
798
 		return
738
 		return
805
 	}
745
 	}
806
 	// send echo-message
746
 	// send echo-message
807
 	if client.capabilities.Has(caps.EchoMessage) {
747
 	if client.capabilities.Has(caps.EchoMessage) {
808
-		var tagsToUse *map[string]ircmsg.TagValue
748
+		var tagsToUse map[string]string
809
 		if client.capabilities.Has(caps.MessageTags) {
749
 		if client.capabilities.Has(caps.MessageTags) {
810
 			tagsToUse = clientOnlyTags
750
 			tagsToUse = clientOnlyTags
811
 		}
751
 		}
812
 		nickMaskString := client.NickMaskString()
752
 		nickMaskString := client.NickMaskString()
813
 		accountName := client.AccountName()
753
 		accountName := client.AccountName()
814
-		if message == nil {
815
-			rb.AddFromClient(msgid, nickMaskString, accountName, tagsToUse, cmd, channel.name)
754
+		if command == "TAGMSG" && client.capabilities.Has(caps.MessageTags) {
755
+			rb.AddFromClient(message.Msgid, nickMaskString, accountName, tagsToUse, command, channel.name)
816
 		} else {
756
 		} else {
817
-			rb.AddSplitMessageFromClient(msgid, nickMaskString, accountName, tagsToUse, cmd, channel.name, *message)
757
+			rb.AddSplitMessageFromClient(nickMaskString, accountName, tagsToUse, command, channel.name, message)
818
 		}
758
 		}
819
 	}
759
 	}
820
 
760
 
832
 		if member == client {
772
 		if member == client {
833
 			continue
773
 			continue
834
 		}
774
 		}
835
-		var tagsToUse *map[string]ircmsg.TagValue
775
+		var tagsToUse map[string]string
836
 		if member.capabilities.Has(caps.MessageTags) {
776
 		if member.capabilities.Has(caps.MessageTags) {
837
 			tagsToUse = clientOnlyTags
777
 			tagsToUse = clientOnlyTags
778
+		} else if command == "TAGMSG" {
779
+			continue
838
 		}
780
 		}
839
 
781
 
840
-		if message == nil {
841
-			member.sendFromClientInternal(false, now, msgid, nickmask, account, tagsToUse, cmd, channel.name)
782
+		if command == "TAGMSG" {
783
+			member.sendFromClientInternal(false, now, message.Msgid, nickmask, account, tagsToUse, command, channel.name)
842
 		} else {
784
 		} else {
843
-			member.sendSplitMsgFromClientInternal(false, now, msgid, nickmask, account, tagsToUse, cmd, channel.name, *message)
785
+			member.sendSplitMsgFromClientInternal(false, now, nickmask, account, tagsToUse, command, channel.name, message)
844
 		}
786
 		}
845
 	}
787
 	}
846
 
788
 
847
 	channel.history.Add(history.Item{
789
 	channel.history.Add(history.Item{
848
 		Type:        histType,
790
 		Type:        histType,
849
-		Msgid:       msgid,
850
-		Message:     *message,
791
+		Message:     message,
851
 		Nick:        nickmask,
792
 		Nick:        nickmask,
852
 		AccountName: account,
793
 		AccountName: account,
853
 		Time:        now,
794
 		Time:        now,
980
 		member.Send(nil, clientMask, "KICK", channel.name, targetNick, comment)
921
 		member.Send(nil, clientMask, "KICK", channel.name, targetNick, comment)
981
 	}
922
 	}
982
 
923
 
924
+	message := utils.SplitMessage{}
925
+	message.Message = comment
926
+	message.Msgid = targetNick // XXX abuse this field
983
 	channel.history.Add(history.Item{
927
 	channel.history.Add(history.Item{
984
 		Type:        history.Kick,
928
 		Type:        history.Kick,
985
 		Nick:        clientMask,
929
 		Nick:        clientMask,
986
-		Message:     utils.MakeSplitMessage(comment, true),
987
 		AccountName: target.AccountName(),
930
 		AccountName: target.AccountName(),
988
-		Msgid:       targetNick, // XXX abuse this field
931
+		Message:     message,
989
 	})
932
 	})
990
 
933
 
991
 	channel.Quit(target)
934
 	channel.Quit(target)

+ 55
- 77
irc/client.go View File

62
 	hasQuit            bool
62
 	hasQuit            bool
63
 	hops               int
63
 	hops               int
64
 	hostname           string
64
 	hostname           string
65
-	idletimer          *IdleTimer
65
+	idletimer          IdleTimer
66
 	invitedTo          map[string]bool
66
 	invitedTo          map[string]bool
67
 	isDestroyed        bool
67
 	isDestroyed        bool
68
 	isTor              bool
68
 	isTor              bool
69
 	isQuitting         bool
69
 	isQuitting         bool
70
 	languages          []string
70
 	languages          []string
71
 	loginThrottle      connection_limits.GenericThrottle
71
 	loginThrottle      connection_limits.GenericThrottle
72
-	maxlenTags         uint32
73
 	maxlenRest         uint32
72
 	maxlenRest         uint32
74
 	nick               string
73
 	nick               string
75
 	nickCasefolded     string
74
 	nickCasefolded     string
122
 func RunNewClient(server *Server, conn clientConn) {
121
 func RunNewClient(server *Server, conn clientConn) {
123
 	now := time.Now()
122
 	now := time.Now()
124
 	config := server.Config()
123
 	config := server.Config()
125
-	fullLineLenLimit := config.Limits.LineLen.Tags + config.Limits.LineLen.Rest
126
-	socket := NewSocket(conn.Conn, fullLineLenLimit*2, config.Server.MaxSendQBytes)
124
+	fullLineLenLimit := ircmsg.MaxlenTagsFromClient + config.Limits.LineLen.Rest
125
+	// give them 1k of grace over the limit:
126
+	socket := NewSocket(conn.Conn, fullLineLenLimit+1024, config.Server.MaxSendQBytes)
127
 	client := &Client{
127
 	client := &Client{
128
 		atime:        now,
128
 		atime:        now,
129
 		capabilities: caps.NewSet(),
129
 		capabilities: caps.NewSet(),
246
 // command goroutine
246
 // command goroutine
247
 //
247
 //
248
 
248
 
249
-func (client *Client) recomputeMaxlens() (int, int) {
250
-	maxlenTags := 512
249
+func (client *Client) recomputeMaxlens() int {
251
 	maxlenRest := 512
250
 	maxlenRest := 512
252
-	if client.capabilities.Has(caps.MessageTags) {
253
-		maxlenTags = 4096
254
-	}
255
 	if client.capabilities.Has(caps.MaxLine) {
251
 	if client.capabilities.Has(caps.MaxLine) {
256
-		limits := client.server.Limits()
257
-		if limits.LineLen.Tags > maxlenTags {
258
-			maxlenTags = limits.LineLen.Tags
259
-		}
260
-		maxlenRest = limits.LineLen.Rest
252
+		maxlenRest = client.server.Limits().LineLen.Rest
261
 	}
253
 	}
262
 
254
 
263
-	atomic.StoreUint32(&client.maxlenTags, uint32(maxlenTags))
264
 	atomic.StoreUint32(&client.maxlenRest, uint32(maxlenRest))
255
 	atomic.StoreUint32(&client.maxlenRest, uint32(maxlenRest))
265
 
256
 
266
-	return maxlenTags, maxlenRest
257
+	return maxlenRest
267
 }
258
 }
268
 
259
 
269
 // allow these negotiated length limits to be read without locks; this is a convenience
260
 // allow these negotiated length limits to be read without locks; this is a convenience
270
 // so that Client.Send doesn't have to acquire any Client locks
261
 // so that Client.Send doesn't have to acquire any Client locks
271
-func (client *Client) maxlens() (int, int) {
272
-	return int(atomic.LoadUint32(&client.maxlenTags)), int(atomic.LoadUint32(&client.maxlenRest))
262
+func (client *Client) MaxlenRest() int {
263
+	return int(atomic.LoadUint32(&client.maxlenRest))
273
 }
264
 }
274
 
265
 
275
 func (client *Client) run() {
266
 func (client *Client) run() {
292
 		client.destroy(false)
283
 		client.destroy(false)
293
 	}()
284
 	}()
294
 
285
 
295
-	client.idletimer = NewIdleTimer(client)
296
-	client.idletimer.Start()
286
+	client.idletimer.Initialize(client)
297
 
287
 
298
 	client.nickTimer.Initialize(client)
288
 	client.nickTimer.Initialize(client)
299
 
289
 
302
 	firstLine := true
292
 	firstLine := true
303
 
293
 
304
 	for {
294
 	for {
305
-		maxlenTags, maxlenRest := client.recomputeMaxlens()
295
+		maxlenRest := client.recomputeMaxlens()
306
 
296
 
307
 		line, err = client.socket.Read()
297
 		line, err = client.socket.Read()
308
 		if err != nil {
298
 		if err != nil {
331
 			}
321
 			}
332
 		}
322
 		}
333
 
323
 
334
-		msg, err = ircmsg.ParseLineMaxLen(line, maxlenTags, maxlenRest)
324
+		msg, err = ircmsg.ParseLineStrict(line, true, maxlenRest)
335
 		if err == ircmsg.ErrorLineIsEmpty {
325
 		if err == ircmsg.ErrorLineIsEmpty {
336
 			continue
326
 			continue
327
+		} else if err == ircmsg.ErrorLineTooLong {
328
+			client.Send(nil, client.server.name, ERR_INPUTTOOLONG, client.nick, client.t("Input line too long"))
329
+			continue
337
 		} else if err != nil {
330
 		} else if err != nil {
338
 			client.Quit(client.t("Received malformed line"))
331
 			client.Quit(client.t("Received malformed line"))
339
 			break
332
 			break
539
 		default:
532
 		default:
540
 			continue
533
 			continue
541
 		}
534
 		}
542
-		var tags Tags
535
+		var tags map[string]string
543
 		if serverTime {
536
 		if serverTime {
544
-			tags = ensureTag(tags, "time", item.Time.Format(IRCv3TimestampFormat))
537
+			tags = map[string]string{"time": item.Time.Format(IRCv3TimestampFormat)}
545
 		}
538
 		}
546
-		rb.AddSplitMessageFromClient(item.Msgid, item.Nick, item.AccountName, tags, command, nick, item.Message)
539
+		rb.AddSplitMessageFromClient(item.Nick, item.AccountName, tags, command, nick, item.Message)
547
 	}
540
 	}
548
 	if !complete {
541
 	if !complete {
549
 		rb.Add(nil, "HistServ", "NOTICE", nick, client.t("Some additional message history may have been lost"))
542
 		rb.Add(nil, "HistServ", "NOTICE", nick, client.t("Some additional message history may have been lost"))
841
 		return
834
 		return
842
 	}
835
 	}
843
 
836
 
844
-	var quitLine string
837
+	var finalData []byte
845
 	// #364: don't send QUIT lines to unregistered clients
838
 	// #364: don't send QUIT lines to unregistered clients
846
 	if registered {
839
 	if registered {
847
 		quitMsg := ircmsg.MakeMessage(nil, prefix, "QUIT", message)
840
 		quitMsg := ircmsg.MakeMessage(nil, prefix, "QUIT", message)
848
-		quitLine, _ = quitMsg.Line()
841
+		finalData, _ = quitMsg.LineBytesStrict(false, 512)
849
 	}
842
 	}
850
 
843
 
851
 	errorMsg := ircmsg.MakeMessage(nil, "", "ERROR", message)
844
 	errorMsg := ircmsg.MakeMessage(nil, "", "ERROR", message)
852
-	errorLine, _ := errorMsg.Line()
845
+	errorMsgBytes, _ := errorMsg.LineBytesStrict(false, 512)
846
+	finalData = append(finalData, errorMsgBytes...)
853
 
847
 
854
-	client.socket.SetFinalData(quitLine + errorLine)
848
+	client.socket.SetFinalData(finalData)
855
 }
849
 }
856
 
850
 
857
 // destroy gets rid of a client, removes them from server lists etc.
851
 // destroy gets rid of a client, removes them from server lists etc.
962
 
956
 
963
 // SendSplitMsgFromClient sends an IRC PRIVMSG/NOTICE coming from a specific client.
957
 // SendSplitMsgFromClient sends an IRC PRIVMSG/NOTICE coming from a specific client.
964
 // Adds account-tag to the line as well.
958
 // Adds account-tag to the line as well.
965
-func (client *Client) SendSplitMsgFromClient(msgid string, from *Client, tags Tags, command, target string, message utils.SplitMessage) {
966
-	client.sendSplitMsgFromClientInternal(false, time.Time{}, msgid, from.NickMaskString(), from.AccountName(), tags, command, target, message)
959
+func (client *Client) SendSplitMsgFromClient(from *Client, tags map[string]string, command, target string, message utils.SplitMessage) {
960
+	client.sendSplitMsgFromClientInternal(false, time.Time{}, from.NickMaskString(), from.AccountName(), tags, command, target, message)
967
 }
961
 }
968
 
962
 
969
-func (client *Client) sendSplitMsgFromClientInternal(blocking bool, serverTime time.Time, msgid string, nickmask, accountName string, tags Tags, command, target string, message utils.SplitMessage) {
963
+func (client *Client) sendSplitMsgFromClientInternal(blocking bool, serverTime time.Time, nickmask, accountName string, tags map[string]string, command, target string, message utils.SplitMessage) {
970
 	if client.capabilities.Has(caps.MaxLine) || message.Wrapped == nil {
964
 	if client.capabilities.Has(caps.MaxLine) || message.Wrapped == nil {
971
-		client.sendFromClientInternal(blocking, serverTime, msgid, nickmask, accountName, tags, command, target, message.Original)
965
+		client.sendFromClientInternal(blocking, serverTime, message.Msgid, nickmask, accountName, tags, command, target, message.Message)
972
 	} else {
966
 	} else {
973
-		for _, str := range message.Wrapped {
974
-			client.sendFromClientInternal(blocking, serverTime, msgid, nickmask, accountName, tags, command, target, str)
967
+		for _, messagePair := range message.Wrapped {
968
+			client.sendFromClientInternal(blocking, serverTime, messagePair.Msgid, nickmask, accountName, tags, command, target, messagePair.Message)
975
 		}
969
 		}
976
 	}
970
 	}
977
 }
971
 }
978
 
972
 
979
 // SendFromClient sends an IRC line coming from a specific client.
973
 // SendFromClient sends an IRC line coming from a specific client.
980
 // Adds account-tag to the line as well.
974
 // Adds account-tag to the line as well.
981
-func (client *Client) SendFromClient(msgid string, from *Client, tags Tags, command string, params ...string) error {
975
+func (client *Client) SendFromClient(msgid string, from *Client, tags map[string]string, command string, params ...string) error {
982
 	return client.sendFromClientInternal(false, time.Time{}, msgid, from.NickMaskString(), from.AccountName(), tags, command, params...)
976
 	return client.sendFromClientInternal(false, time.Time{}, msgid, from.NickMaskString(), from.AccountName(), tags, command, params...)
983
 }
977
 }
984
 
978
 
985
-// helper to add a tag to `tags` (or create a new tag set if the current one is nil)
986
-func ensureTag(tags Tags, tagName, tagValue string) (result Tags) {
987
-	if tags == nil {
988
-		result = ircmsg.MakeTags(tagName, tagValue)
989
-	} else {
990
-		result = tags
991
-		(*tags)[tagName] = ircmsg.MakeTagValue(tagValue)
992
-	}
993
-	return
994
-}
995
-
996
-// XXX this is a hack where we allow overriding the client's nickmask
997
-// this is to support CHGHOST, which requires that we send the *original* nickmask with the response
998
-func (client *Client) sendFromClientInternal(blocking bool, serverTime time.Time, msgid string, nickmask, accountName string, tags Tags, command string, params ...string) error {
979
+// this is SendFromClient, but directly exposing nickmask and accountName,
980
+// for things like history replay and CHGHOST where they no longer (necessarily)
981
+// correspond to the current state of a client
982
+func (client *Client) sendFromClientInternal(blocking bool, serverTime time.Time, msgid string, nickmask, accountName string, tags map[string]string, command string, params ...string) error {
983
+	msg := ircmsg.MakeMessage(tags, nickmask, command, params...)
999
 	// attach account-tag
984
 	// attach account-tag
1000
 	if client.capabilities.Has(caps.AccountTag) && accountName != "*" {
985
 	if client.capabilities.Has(caps.AccountTag) && accountName != "*" {
1001
-		tags = ensureTag(tags, "account", accountName)
986
+		msg.SetTag("account", accountName)
1002
 	}
987
 	}
1003
 	// attach message-id
988
 	// attach message-id
1004
-	if len(msgid) > 0 && client.capabilities.Has(caps.MessageTags) {
1005
-		tags = ensureTag(tags, "draft/msgid", msgid)
989
+	if msgid != "" && client.capabilities.Has(caps.MessageTags) {
990
+		msg.SetTag("draft/msgid", msgid)
991
+	}
992
+	// attach server-time
993
+	if client.capabilities.Has(caps.ServerTime) {
994
+		msg.SetTag("time", time.Now().UTC().Format(IRCv3TimestampFormat))
1006
 	}
995
 	}
1007
 
996
 
1008
-	return client.sendInternal(blocking, serverTime, tags, nickmask, command, params...)
997
+	return client.SendRawMessage(msg, blocking)
1009
 }
998
 }
1010
 
999
 
1011
 var (
1000
 var (
1025
 func (client *Client) SendRawMessage(message ircmsg.IrcMessage, blocking bool) error {
1014
 func (client *Client) SendRawMessage(message ircmsg.IrcMessage, blocking bool) error {
1026
 	// use dumb hack to force the last param to be a trailing param if required
1015
 	// use dumb hack to force the last param to be a trailing param if required
1027
 	var usedTrailingHack bool
1016
 	var usedTrailingHack bool
1028
-	if commandsThatMustUseTrailing[strings.ToUpper(message.Command)] && len(message.Params) > 0 {
1017
+	if commandsThatMustUseTrailing[message.Command] && len(message.Params) > 0 {
1029
 		lastParam := message.Params[len(message.Params)-1]
1018
 		lastParam := message.Params[len(message.Params)-1]
1030
 		// to force trailing, we ensure the final param contains a space
1019
 		// to force trailing, we ensure the final param contains a space
1031
-		if !strings.Contains(lastParam, " ") {
1020
+		if strings.IndexByte(lastParam, ' ') == -1 {
1032
 			message.Params[len(message.Params)-1] = lastParam + " "
1021
 			message.Params[len(message.Params)-1] = lastParam + " "
1033
 			usedTrailingHack = true
1022
 			usedTrailingHack = true
1034
 		}
1023
 		}
1035
 	}
1024
 	}
1036
 
1025
 
1037
 	// assemble message
1026
 	// assemble message
1038
-	maxlenTags, maxlenRest := client.maxlens()
1039
-	line, err := message.LineMaxLenBytes(maxlenTags, maxlenRest)
1027
+	maxlenRest := client.MaxlenRest()
1028
+	line, err := message.LineBytesStrict(false, maxlenRest)
1040
 	if err != nil {
1029
 	if err != nil {
1041
 		logline := fmt.Sprintf("Error assembling message for sending: %v\n%s", err, debug.Stack())
1030
 		logline := fmt.Sprintf("Error assembling message for sending: %v\n%s", err, debug.Stack())
1042
 		client.server.logger.Error("internal", logline)
1031
 		client.server.logger.Error("internal", logline)
1043
 
1032
 
1044
 		message = ircmsg.MakeMessage(nil, client.server.name, ERR_UNKNOWNERROR, "*", "Error assembling message for sending")
1033
 		message = ircmsg.MakeMessage(nil, client.server.name, ERR_UNKNOWNERROR, "*", "Error assembling message for sending")
1045
-		line, _ := message.LineBytes()
1034
+		line, _ := message.LineBytesStrict(false, 0)
1046
 
1035
 
1047
 		if blocking {
1036
 		if blocking {
1048
 			client.socket.BlockingWrite(line)
1037
 			client.socket.BlockingWrite(line)
1054
 
1043
 
1055
 	// if we used the trailing hack, we need to strip the final space we appended earlier on
1044
 	// if we used the trailing hack, we need to strip the final space we appended earlier on
1056
 	if usedTrailingHack {
1045
 	if usedTrailingHack {
1057
-		copy(line[len(line)-3:], []byte{'\r', '\n'})
1046
+		copy(line[len(line)-3:], "\r\n")
1058
 		line = line[:len(line)-1]
1047
 		line = line[:len(line)-1]
1059
 	}
1048
 	}
1060
 
1049
 
1070
 	}
1059
 	}
1071
 }
1060
 }
1072
 
1061
 
1073
-func (client *Client) sendInternal(blocking bool, serverTime time.Time, tags Tags, prefix string, command string, params ...string) error {
1074
-	// attach server time
1075
-	if client.capabilities.Has(caps.ServerTime) {
1076
-		if serverTime.IsZero() {
1077
-			serverTime = time.Now()
1078
-		}
1079
-		tags = ensureTag(tags, "time", serverTime.UTC().Format(IRCv3TimestampFormat))
1080
-	}
1081
-
1082
-	// send out the message
1083
-	message := ircmsg.MakeMessage(tags, prefix, command, params...)
1084
-	client.SendRawMessage(message, blocking)
1085
-	return nil
1086
-}
1087
-
1088
 // Send sends an IRC line to the client.
1062
 // Send sends an IRC line to the client.
1089
-func (client *Client) Send(tags Tags, prefix string, command string, params ...string) error {
1090
-	return client.sendInternal(false, time.Time{}, tags, prefix, command, params...)
1063
+func (client *Client) Send(tags map[string]string, prefix string, command string, params ...string) error {
1064
+	msg := ircmsg.MakeMessage(tags, prefix, command, params...)
1065
+	if client.capabilities.Has(caps.ServerTime) && !msg.HasTag("time") {
1066
+		msg.SetTag("time", time.Now().UTC().Format(IRCv3TimestampFormat))
1067
+	}
1068
+	return client.SendRawMessage(msg, false)
1091
 }
1069
 }
1092
 
1070
 
1093
 // Notice sends the client a notice from the server.
1071
 // Notice sends the client a notice from the server.

+ 0
- 4
irc/config.go View File

202
 
202
 
203
 // LineLenConfig controls line lengths.
203
 // LineLenConfig controls line lengths.
204
 type LineLenLimits struct {
204
 type LineLenLimits struct {
205
-	Tags int
206
 	Rest int
205
 	Rest int
207
 }
206
 }
208
 
207
 
553
 	}
552
 	}
554
 	config.Server.WebIRC = newWebIRC
553
 	config.Server.WebIRC = newWebIRC
555
 	// process limits
554
 	// process limits
556
-	if config.Limits.LineLen.Tags < 512 {
557
-		config.Limits.LineLen.Tags = 512
558
-	}
559
 	if config.Limits.LineLen.Rest < 512 {
555
 	if config.Limits.LineLen.Rest < 512 {
560
 		config.Limits.LineLen.Rest = 512
556
 		config.Limits.LineLen.Rest = 512
561
 	}
557
 	}

+ 15
- 23
irc/handlers.go View File

1874
 
1874
 
1875
 // NOTICE <target>{,<target>} <message>
1875
 // NOTICE <target>{,<target>} <message>
1876
 func noticeHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool {
1876
 func noticeHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool {
1877
-	clientOnlyTags := utils.GetClientOnlyTags(msg.Tags)
1877
+	clientOnlyTags := msg.ClientOnlyTags()
1878
 	targets := strings.Split(msg.Params[0], ",")
1878
 	targets := strings.Split(msg.Params[0], ",")
1879
 	message := msg.Params[1]
1879
 	message := msg.Params[1]
1880
 
1880
 
1883
 		return false
1883
 		return false
1884
 	}
1884
 	}
1885
 
1885
 
1886
-	// split privmsg
1887
 	splitMsg := utils.MakeSplitMessage(message, !client.capabilities.Has(caps.MaxLine))
1886
 	splitMsg := utils.MakeSplitMessage(message, !client.capabilities.Has(caps.MaxLine))
1888
 
1887
 
1889
 	for i, targetString := range targets {
1888
 	for i, targetString := range targets {
1905
 				// errors silently ignored with NOTICE as per RFC
1904
 				// errors silently ignored with NOTICE as per RFC
1906
 				continue
1905
 				continue
1907
 			}
1906
 			}
1908
-			msgid := server.generateMessageID()
1909
-			channel.SplitNotice(msgid, lowestPrefix, clientOnlyTags, client, splitMsg, rb)
1907
+			channel.SendSplitMessage("NOTICE", lowestPrefix, clientOnlyTags, client, splitMsg, rb)
1910
 		} else {
1908
 		} else {
1911
 			target, err := CasefoldName(targetString)
1909
 			target, err := CasefoldName(targetString)
1912
 			if err != nil {
1910
 			if err != nil {
1926
 			if !user.capabilities.Has(caps.MessageTags) {
1924
 			if !user.capabilities.Has(caps.MessageTags) {
1927
 				clientOnlyTags = nil
1925
 				clientOnlyTags = nil
1928
 			}
1926
 			}
1929
-			msgid := server.generateMessageID()
1930
 			// restrict messages appropriately when +R is set
1927
 			// restrict messages appropriately when +R is set
1931
 			// intentionally make the sending user think the message went through fine
1928
 			// intentionally make the sending user think the message went through fine
1932
 			allowedPlusR := !user.HasMode(modes.RegisteredOnly) || client.LoggedIntoAccount()
1929
 			allowedPlusR := !user.HasMode(modes.RegisteredOnly) || client.LoggedIntoAccount()
1933
 			allowedTor := !user.isTor || !isRestrictedCTCPMessage(message)
1930
 			allowedTor := !user.isTor || !isRestrictedCTCPMessage(message)
1934
 			if allowedPlusR && allowedTor {
1931
 			if allowedPlusR && allowedTor {
1935
-				user.SendSplitMsgFromClient(msgid, client, clientOnlyTags, "NOTICE", user.nick, splitMsg)
1932
+				user.SendSplitMsgFromClient(client, clientOnlyTags, "NOTICE", user.nick, splitMsg)
1936
 			}
1933
 			}
1937
 			nickMaskString := client.NickMaskString()
1934
 			nickMaskString := client.NickMaskString()
1938
 			accountName := client.AccountName()
1935
 			accountName := client.AccountName()
1939
 			if client.capabilities.Has(caps.EchoMessage) {
1936
 			if client.capabilities.Has(caps.EchoMessage) {
1940
-				rb.AddSplitMessageFromClient(msgid, nickMaskString, accountName, clientOnlyTags, "NOTICE", user.nick, splitMsg)
1937
+				rb.AddSplitMessageFromClient(nickMaskString, accountName, clientOnlyTags, "NOTICE", user.nick, splitMsg)
1941
 			}
1938
 			}
1942
 
1939
 
1943
 			user.history.Add(history.Item{
1940
 			user.history.Add(history.Item{
1944
 				Type:        history.Notice,
1941
 				Type:        history.Notice,
1945
-				Msgid:       msgid,
1946
 				Message:     splitMsg,
1942
 				Message:     splitMsg,
1947
 				Nick:        nickMaskString,
1943
 				Nick:        nickMaskString,
1948
 				AccountName: accountName,
1944
 				AccountName: accountName,
2096
 
2092
 
2097
 // PRIVMSG <target>{,<target>} <message>
2093
 // PRIVMSG <target>{,<target>} <message>
2098
 func privmsgHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool {
2094
 func privmsgHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool {
2099
-	clientOnlyTags := utils.GetClientOnlyTags(msg.Tags)
2095
+	clientOnlyTags := msg.ClientOnlyTags()
2100
 	targets := strings.Split(msg.Params[0], ",")
2096
 	targets := strings.Split(msg.Params[0], ",")
2101
 	message := msg.Params[1]
2097
 	message := msg.Params[1]
2102
 
2098
 
2133
 				rb.Add(nil, client.server.name, ERR_CANNOTSENDTOCHAN, channel.name, client.t("Cannot send to channel"))
2129
 				rb.Add(nil, client.server.name, ERR_CANNOTSENDTOCHAN, channel.name, client.t("Cannot send to channel"))
2134
 				continue
2130
 				continue
2135
 			}
2131
 			}
2136
-			msgid := server.generateMessageID()
2137
-			channel.SplitPrivMsg(msgid, lowestPrefix, clientOnlyTags, client, splitMsg, rb)
2132
+			channel.SendSplitMessage("PRIVMSG", lowestPrefix, clientOnlyTags, client, splitMsg, rb)
2138
 		} else {
2133
 		} else {
2139
 			target, err = CasefoldName(targetString)
2134
 			target, err = CasefoldName(targetString)
2140
 			if service, isService := OragonoServices[target]; isService {
2135
 			if service, isService := OragonoServices[target]; isService {
2151
 			if !user.capabilities.Has(caps.MessageTags) {
2146
 			if !user.capabilities.Has(caps.MessageTags) {
2152
 				clientOnlyTags = nil
2147
 				clientOnlyTags = nil
2153
 			}
2148
 			}
2154
-			msgid := server.generateMessageID()
2155
 			// restrict messages appropriately when +R is set
2149
 			// restrict messages appropriately when +R is set
2156
 			// intentionally make the sending user think the message went through fine
2150
 			// intentionally make the sending user think the message went through fine
2157
 			allowedPlusR := !user.HasMode(modes.RegisteredOnly) || client.LoggedIntoAccount()
2151
 			allowedPlusR := !user.HasMode(modes.RegisteredOnly) || client.LoggedIntoAccount()
2158
 			allowedTor := !user.isTor || !isRestrictedCTCPMessage(message)
2152
 			allowedTor := !user.isTor || !isRestrictedCTCPMessage(message)
2159
 			if allowedPlusR && allowedTor {
2153
 			if allowedPlusR && allowedTor {
2160
-				user.SendSplitMsgFromClient(msgid, client, clientOnlyTags, "PRIVMSG", user.nick, splitMsg)
2154
+				user.SendSplitMsgFromClient(client, clientOnlyTags, "PRIVMSG", user.nick, splitMsg)
2161
 			}
2155
 			}
2162
 			nickMaskString := client.NickMaskString()
2156
 			nickMaskString := client.NickMaskString()
2163
 			accountName := client.AccountName()
2157
 			accountName := client.AccountName()
2164
 			if client.capabilities.Has(caps.EchoMessage) {
2158
 			if client.capabilities.Has(caps.EchoMessage) {
2165
-				rb.AddSplitMessageFromClient(msgid, nickMaskString, accountName, clientOnlyTags, "PRIVMSG", user.nick, splitMsg)
2159
+				rb.AddSplitMessageFromClient(nickMaskString, accountName, clientOnlyTags, "PRIVMSG", user.nick, splitMsg)
2166
 			}
2160
 			}
2167
 			if user.HasMode(modes.Away) {
2161
 			if user.HasMode(modes.Away) {
2168
 				//TODO(dan): possibly implement cooldown of away notifications to users
2162
 				//TODO(dan): possibly implement cooldown of away notifications to users
2171
 
2165
 
2172
 			user.history.Add(history.Item{
2166
 			user.history.Add(history.Item{
2173
 				Type:        history.Privmsg,
2167
 				Type:        history.Privmsg,
2174
-				Msgid:       msgid,
2175
 				Message:     splitMsg,
2168
 				Message:     splitMsg,
2176
 				Nick:        nickMaskString,
2169
 				Nick:        nickMaskString,
2177
 				AccountName: accountName,
2170
 				AccountName: accountName,
2353
 
2346
 
2354
 // TAGMSG <target>{,<target>}
2347
 // TAGMSG <target>{,<target>}
2355
 func tagmsgHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool {
2348
 func tagmsgHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool {
2356
-	clientOnlyTags := utils.GetClientOnlyTags(msg.Tags)
2349
+	clientOnlyTags := msg.ClientOnlyTags()
2357
 	// no client-only tags, so we can drop it
2350
 	// no client-only tags, so we can drop it
2358
 	if clientOnlyTags == nil {
2351
 	if clientOnlyTags == nil {
2359
 		return false
2352
 		return false
2362
 	targets := strings.Split(msg.Params[0], ",")
2355
 	targets := strings.Split(msg.Params[0], ",")
2363
 
2356
 
2364
 	cnick := client.Nick()
2357
 	cnick := client.Nick()
2358
+	message := utils.MakeSplitMessage("", true) // assign consistent message ID
2365
 	for i, targetString := range targets {
2359
 	for i, targetString := range targets {
2366
 		// max of four targets per privmsg
2360
 		// max of four targets per privmsg
2367
 		if i > maxTargets-1 {
2361
 		if i > maxTargets-1 {
2386
 				rb.Add(nil, client.server.name, ERR_CANNOTSENDTOCHAN, channel.name, client.t("Cannot send to channel"))
2380
 				rb.Add(nil, client.server.name, ERR_CANNOTSENDTOCHAN, channel.name, client.t("Cannot send to channel"))
2387
 				continue
2381
 				continue
2388
 			}
2382
 			}
2389
-			msgid := server.generateMessageID()
2390
-
2391
-			channel.TagMsg(msgid, lowestPrefix, clientOnlyTags, client, rb)
2383
+			channel.SendSplitMessage("TAGMSG", lowestPrefix, clientOnlyTags, client, message, rb)
2392
 		} else {
2384
 		} else {
2393
 			target, err = CasefoldName(targetString)
2385
 			target, err = CasefoldName(targetString)
2394
 			user := server.clients.Get(target)
2386
 			user := server.clients.Get(target)
2398
 				}
2390
 				}
2399
 				continue
2391
 				continue
2400
 			}
2392
 			}
2401
-			msgid := server.generateMessageID()
2402
 
2393
 
2403
 			// end user can't receive tagmsgs
2394
 			// end user can't receive tagmsgs
2404
 			if !user.capabilities.Has(caps.MessageTags) {
2395
 			if !user.capabilities.Has(caps.MessageTags) {
2405
 				continue
2396
 				continue
2406
 			}
2397
 			}
2407
-			user.SendFromClient(msgid, client, clientOnlyTags, "TAGMSG", user.nick)
2398
+			unick := user.Nick()
2399
+			user.SendSplitMsgFromClient(client, clientOnlyTags, "TAGMSG", unick, message)
2408
 			if client.capabilities.Has(caps.EchoMessage) {
2400
 			if client.capabilities.Has(caps.EchoMessage) {
2409
-				rb.AddFromClient(msgid, client.NickMaskString(), client.AccountName(), clientOnlyTags, "TAGMSG", user.nick)
2401
+				rb.AddSplitMessageFromClient(client.NickMaskString(), client.AccountName(), clientOnlyTags, "TAGMSG", unick, message)
2410
 			}
2402
 			}
2411
 			if user.HasMode(modes.Away) {
2403
 			if user.HasMode(modes.Away) {
2412
 				//TODO(dan): possibly implement cooldown of away notifications to users
2404
 				//TODO(dan): possibly implement cooldown of away notifications to users
2413
-				rb.Add(nil, server.name, RPL_AWAY, cnick, user.Nick(), user.AwayMessage())
2405
+				rb.Add(nil, server.name, RPL_AWAY, cnick, unick, user.AwayMessage())
2414
 			}
2406
 			}
2415
 		}
2407
 		}
2416
 	}
2408
 	}

+ 2
- 2
irc/history/history.go View File

21
 	Kick
21
 	Kick
22
 	Quit
22
 	Quit
23
 	Mode
23
 	Mode
24
+	Tagmsg
24
 )
25
 )
25
 
26
 
26
 // Item represents an event (e.g., a PRIVMSG or a JOIN) and its associated data
27
 // Item represents an event (e.g., a PRIVMSG or a JOIN) and its associated data
33
 	AccountName string
34
 	AccountName string
34
 	Message     utils.SplitMessage
35
 	Message     utils.SplitMessage
35
 	// for non-privmsg items, we may stuff some other data in here
36
 	// for non-privmsg items, we may stuff some other data in here
36
-	Msgid string
37
 }
37
 }
38
 
38
 
39
 // HasMsgid tests whether a message has the message id `msgid`.
39
 // HasMsgid tests whether a message has the message id `msgid`.
40
 func (item *Item) HasMsgid(msgid string) bool {
40
 func (item *Item) HasMsgid(msgid string) bool {
41
 	// XXX we stuff other data in the Msgid field sometimes,
41
 	// XXX we stuff other data in the Msgid field sometimes,
42
 	// don't match it by accident
42
 	// don't match it by accident
43
-	return (item.Type == Privmsg || item.Type == Notice) && item.Msgid == msgid
43
+	return (item.Type == Privmsg || item.Type == Notice) && item.Message.Msgid == msgid
44
 }
44
 }
45
 
45
 
46
 type Predicate func(item Item) (matches bool)
46
 type Predicate func(item Item) (matches bool)

+ 10
- 16
irc/idletimer.go View File

54
 	timer       *time.Timer
54
 	timer       *time.Timer
55
 }
55
 }
56
 
56
 
57
-// NewIdleTimer sets up a new IdleTimer using constant timeouts.
58
-func NewIdleTimer(client *Client) *IdleTimer {
59
-	it := IdleTimer{
60
-		registerTimeout: RegisterTimeout,
61
-		client:          client,
62
-	}
57
+// Initialize sets up an IdleTimer and starts counting idle time;
58
+// if there is no activity from the client, it will eventually be stopped.
59
+func (it *IdleTimer) Initialize(client *Client) {
60
+	it.client = client
61
+	it.registerTimeout = RegisterTimeout
63
 	it.idleTimeout, it.quitTimeout = it.recomputeDurations()
62
 	it.idleTimeout, it.quitTimeout = it.recomputeDurations()
64
-	return &it
63
+
64
+	it.Lock()
65
+	defer it.Unlock()
66
+	it.state = TimerUnregistered
67
+	it.resetTimeout()
65
 }
68
 }
66
 
69
 
67
 // recomputeDurations recomputes the idle and quit durations, given the client's caps.
70
 // recomputeDurations recomputes the idle and quit durations, given the client's caps.
82
 	return
85
 	return
83
 }
86
 }
84
 
87
 
85
-// Start starts counting idle time; if there is no activity from the client,
86
-// it will eventually be stopped.
87
-func (it *IdleTimer) Start() {
88
-	it.Lock()
89
-	defer it.Unlock()
90
-	it.state = TimerUnregistered
91
-	it.resetTimeout()
92
-}
93
-
94
 func (it *IdleTimer) Touch() {
88
 func (it *IdleTimer) Touch() {
95
 	idleTimeout, quitTimeout := it.recomputeDurations()
89
 	idleTimeout, quitTimeout := it.recomputeDurations()
96
 
90
 

+ 3
- 2
irc/languages/languages.go View File

175
 		tlist = append(tlist, fmt.Sprintf("%s (%s): %s", info.Name, info.Code, info.Contributors))
175
 		tlist = append(tlist, fmt.Sprintf("%s (%s): %s", info.Name, info.Code, info.Contributors))
176
 	}
176
 	}
177
 
177
 
178
-	sort.Sort(tlist)
178
+	tlist.Sort()
179
 	return tlist
179
 	return tlist
180
 }
180
 }
181
 
181
 
228
 }
228
 }
229
 
229
 
230
 func (lm *Manager) CapValue() string {
230
 func (lm *Manager) CapValue() string {
231
-	langCodes := make([]string, len(lm.Languages)+1)
231
+	langCodes := make(sort.StringSlice, len(lm.Languages)+1)
232
 	langCodes[0] = strconv.Itoa(len(lm.Languages))
232
 	langCodes[0] = strconv.Itoa(len(lm.Languages))
233
 	i := 1
233
 	i := 1
234
 	for _, info := range lm.Languages {
234
 	for _, info := range lm.Languages {
239
 		langCodes[i] = codeToken
239
 		langCodes[i] = codeToken
240
 		i += 1
240
 		i += 1
241
 	}
241
 	}
242
+	langCodes.Sort()
242
 	return strings.Join(langCodes, ",")
243
 	return strings.Join(langCodes, ",")
243
 }
244
 }

+ 1
- 0
irc/numerics.go View File

119
 	ERR_NOTOPLEVEL                  = "413"
119
 	ERR_NOTOPLEVEL                  = "413"
120
 	ERR_WILDTOPLEVEL                = "414"
120
 	ERR_WILDTOPLEVEL                = "414"
121
 	ERR_BADMASK                     = "415"
121
 	ERR_BADMASK                     = "415"
122
+	ERR_INPUTTOOLONG                = "417"
122
 	ERR_UNKNOWNCOMMAND              = "421"
123
 	ERR_UNKNOWNCOMMAND              = "421"
123
 	ERR_NOMOTD                      = "422"
124
 	ERR_NOMOTD                      = "422"
124
 	ERR_NOADMININFO                 = "423"
125
 	ERR_NOADMININFO                 = "423"

+ 26
- 24
irc/responsebuffer.go View File

32
 
32
 
33
 // GetLabel returns the label from the given message.
33
 // GetLabel returns the label from the given message.
34
 func GetLabel(msg ircmsg.IrcMessage) string {
34
 func GetLabel(msg ircmsg.IrcMessage) string {
35
-	return msg.Tags[caps.LabelTagName].Value
35
+	_, value := msg.GetTag(caps.LabelTagName)
36
+	return value
36
 }
37
 }
37
 
38
 
38
 // NewResponseBuffer returns a new ResponseBuffer.
39
 // NewResponseBuffer returns a new ResponseBuffer.
42
 	}
43
 	}
43
 }
44
 }
44
 
45
 
45
-// Add adds a standard new message to our queue.
46
-func (rb *ResponseBuffer) Add(tags *map[string]ircmsg.TagValue, prefix string, command string, params ...string) {
46
+func (rb *ResponseBuffer) AddMessage(msg ircmsg.IrcMessage) {
47
 	if rb.finalized {
47
 	if rb.finalized {
48
 		rb.target.server.logger.Error("internal", "message added to finalized ResponseBuffer, undefined behavior")
48
 		rb.target.server.logger.Error("internal", "message added to finalized ResponseBuffer, undefined behavior")
49
 		debug.PrintStack()
49
 		debug.PrintStack()
52
 		return
52
 		return
53
 	}
53
 	}
54
 
54
 
55
-	message := ircmsg.MakeMessage(tags, prefix, command, params...)
56
-	rb.messages = append(rb.messages, message)
55
+	rb.messages = append(rb.messages, msg)
56
+}
57
+
58
+// Add adds a standard new message to our queue.
59
+func (rb *ResponseBuffer) Add(tags map[string]string, prefix string, command string, params ...string) {
60
+	rb.AddMessage(ircmsg.MakeMessage(tags, prefix, command, params...))
57
 }
61
 }
58
 
62
 
59
 // AddFromClient adds a new message from a specific client to our queue.
63
 // AddFromClient adds a new message from a specific client to our queue.
60
-func (rb *ResponseBuffer) AddFromClient(msgid string, fromNickMask string, fromAccount string, tags *map[string]ircmsg.TagValue, command string, params ...string) {
64
+func (rb *ResponseBuffer) AddFromClient(msgid string, fromNickMask string, fromAccount string, tags map[string]string, command string, params ...string) {
65
+	msg := ircmsg.MakeMessage(nil, fromNickMask, command, params...)
66
+	msg.UpdateTags(tags)
67
+
61
 	// attach account-tag
68
 	// attach account-tag
62
-	if rb.target.capabilities.Has(caps.AccountTag) {
63
-		if fromAccount != "*" {
64
-			tags = ensureTag(tags, "account", fromAccount)
65
-		}
69
+	if rb.target.capabilities.Has(caps.AccountTag) && fromAccount != "*" {
70
+		msg.SetTag("account", fromAccount)
66
 	}
71
 	}
67
 	// attach message-id
72
 	// attach message-id
68
 	if len(msgid) > 0 && rb.target.capabilities.Has(caps.MessageTags) {
73
 	if len(msgid) > 0 && rb.target.capabilities.Has(caps.MessageTags) {
69
-		tags = ensureTag(tags, "draft/msgid", msgid)
74
+		msg.SetTag("draft/msgid", msgid)
70
 	}
75
 	}
71
 
76
 
72
-	rb.Add(tags, fromNickMask, command, params...)
77
+	rb.AddMessage(msg)
73
 }
78
 }
74
 
79
 
75
 // AddSplitMessageFromClient adds a new split message from a specific client to our queue.
80
 // AddSplitMessageFromClient adds a new split message from a specific client to our queue.
76
-func (rb *ResponseBuffer) AddSplitMessageFromClient(msgid string, fromNickMask string, fromAccount string, tags *map[string]ircmsg.TagValue, command string, target string, message utils.SplitMessage) {
81
+func (rb *ResponseBuffer) AddSplitMessageFromClient(fromNickMask string, fromAccount string, tags map[string]string, command string, target string, message utils.SplitMessage) {
77
 	if rb.target.capabilities.Has(caps.MaxLine) || message.Wrapped == nil {
82
 	if rb.target.capabilities.Has(caps.MaxLine) || message.Wrapped == nil {
78
-		rb.AddFromClient(msgid, fromNickMask, fromAccount, tags, command, target, message.Original)
83
+		rb.AddFromClient(message.Msgid, fromNickMask, fromAccount, tags, command, target, message.Message)
79
 	} else {
84
 	} else {
80
-		for _, str := range message.Wrapped {
81
-			rb.AddFromClient(msgid, fromNickMask, fromAccount, tags, command, target, str)
85
+		for _, messagePair := range message.Wrapped {
86
+			rb.AddFromClient(messagePair.Msgid, fromNickMask, fromAccount, tags, command, target, messagePair.Message)
82
 		}
87
 		}
83
 	}
88
 	}
84
 }
89
 }
103
 
108
 
104
 	message := ircmsg.MakeMessage(nil, rb.target.server.name, "BATCH", "+"+rb.batchID, batchType)
109
 	message := ircmsg.MakeMessage(nil, rb.target.server.name, "BATCH", "+"+rb.batchID, batchType)
105
 	if rb.Label != "" {
110
 	if rb.Label != "" {
106
-		message.Tags[caps.LabelTagName] = ircmsg.MakeTagValue(rb.Label)
111
+		message.SetTag(caps.LabelTagName, rb.Label)
107
 	}
112
 	}
108
 	rb.target.SendRawMessage(message, blocking)
113
 	rb.target.SendRawMessage(message, blocking)
109
 }
114
 }
149
 
154
 
150
 	// if label but no batch, add label to first message
155
 	// if label but no batch, add label to first message
151
 	if useLabel && !useBatch && len(rb.messages) == 1 && rb.batchID == "" {
156
 	if useLabel && !useBatch && len(rb.messages) == 1 && rb.batchID == "" {
152
-		rb.messages[0].Tags[caps.LabelTagName] = ircmsg.MakeTagValue(rb.Label)
157
+		rb.messages[0].SetTag(caps.LabelTagName, rb.Label)
153
 	} else if useBatch {
158
 	} else if useBatch {
154
 		rb.sendBatchStart(defaultBatchType, blocking)
159
 		rb.sendBatchStart(defaultBatchType, blocking)
155
 	}
160
 	}
157
 	// send each message out
162
 	// send each message out
158
 	for _, message := range rb.messages {
163
 	for _, message := range rb.messages {
159
 		// attach server-time if needed
164
 		// attach server-time if needed
160
-		if rb.target.capabilities.Has(caps.ServerTime) {
161
-			if !message.Tags["time"].HasValue {
162
-				t := time.Now().UTC().Format(IRCv3TimestampFormat)
163
-				message.Tags["time"] = ircmsg.MakeTagValue(t)
164
-			}
165
+		if rb.target.capabilities.Has(caps.ServerTime) && !message.HasTag("time") {
166
+			message.SetTag("time", time.Now().UTC().Format(IRCv3TimestampFormat))
165
 		}
167
 		}
166
 
168
 
167
 		// attach batch ID
169
 		// attach batch ID
168
 		if rb.batchID != "" {
170
 		if rb.batchID != "" {
169
-			message.Tags["batch"] = ircmsg.MakeTagValue(rb.batchID)
171
+			message.SetTag("batch", rb.batchID)
170
 		}
172
 		}
171
 
173
 
172
 		// send message out
174
 		// send message out

+ 3
- 8
irc/server.go View File

387
 	return &wrapper, nil
387
 	return &wrapper, nil
388
 }
388
 }
389
 
389
 
390
-// generateMessageID returns a network-unique message ID.
391
-func (server *Server) generateMessageID() string {
392
-	return utils.GenerateSecretToken()
393
-}
394
-
395
 //
390
 //
396
 // server functionality
391
 // server functionality
397
 //
392
 //
623
 	} else {
618
 	} else {
624
 		// enforce configs that can't be changed after launch:
619
 		// enforce configs that can't be changed after launch:
625
 		currentLimits := server.Limits()
620
 		currentLimits := server.Limits()
626
-		if currentLimits.LineLen.Tags != config.Limits.LineLen.Tags || currentLimits.LineLen.Rest != config.Limits.LineLen.Rest {
621
+		if currentLimits.LineLen.Rest != config.Limits.LineLen.Rest {
627
 			return fmt.Errorf("Maximum line length (linelen) cannot be changed after launching the server, rehash aborted")
622
 			return fmt.Errorf("Maximum line length (linelen) cannot be changed after launching the server, rehash aborted")
628
 		} else if server.name != config.Server.Name {
623
 		} else if server.name != config.Server.Name {
629
 			return fmt.Errorf("Server name cannot be changed after launching the server, rehash aborted")
624
 			return fmt.Errorf("Server name cannot be changed after launching the server, rehash aborted")
703
 	}
698
 	}
704
 
699
 
705
 	// MaxLine
700
 	// MaxLine
706
-	if config.Limits.LineLen.Tags != 512 || config.Limits.LineLen.Rest != 512 {
701
+	if config.Limits.LineLen.Rest != 512 {
707
 		SupportedCapabilities.Enable(caps.MaxLine)
702
 		SupportedCapabilities.Enable(caps.MaxLine)
708
-		value := fmt.Sprintf("%d,%d", config.Limits.LineLen.Tags, config.Limits.LineLen.Rest)
703
+		value := fmt.Sprintf("%d", config.Limits.LineLen.Rest)
709
 		CapValues.Set(caps.MaxLine, value)
704
 		CapValues.Set(caps.MaxLine, value)
710
 	}
705
 	}
711
 
706
 

+ 7
- 5
irc/socket.go View File

20
 var (
20
 var (
21
 	handshakeTimeout, _ = time.ParseDuration("5s")
21
 	handshakeTimeout, _ = time.ParseDuration("5s")
22
 	errSendQExceeded    = errors.New("SendQ exceeded")
22
 	errSendQExceeded    = errors.New("SendQ exceeded")
23
+
24
+	sendQExceededMessage = []byte("\r\nERROR :SendQ Exceeded\r\n")
23
 )
25
 )
24
 
26
 
25
 // Socket represents an IRC socket.
27
 // Socket represents an IRC socket.
38
 	totalLength   int
40
 	totalLength   int
39
 	closed        bool
41
 	closed        bool
40
 	sendQExceeded bool
42
 	sendQExceeded bool
41
-	finalData     string // what to send when we die
43
+	finalData     []byte // what to send when we die
42
 	finalized     bool
44
 	finalized     bool
43
 }
45
 }
44
 
46
 
196
 }
198
 }
197
 
199
 
198
 // SetFinalData sets the final data to send when the SocketWriter closes.
200
 // SetFinalData sets the final data to send when the SocketWriter closes.
199
-func (socket *Socket) SetFinalData(data string) {
201
+func (socket *Socket) SetFinalData(data []byte) {
200
 	socket.Lock()
202
 	socket.Lock()
201
 	defer socket.Unlock()
203
 	defer socket.Unlock()
202
 	socket.finalData = data
204
 	socket.finalData = data
271
 	socket.finalized = true
273
 	socket.finalized = true
272
 	finalData := socket.finalData
274
 	finalData := socket.finalData
273
 	if socket.sendQExceeded {
275
 	if socket.sendQExceeded {
274
-		finalData = "\r\nERROR :SendQ Exceeded\r\n"
276
+		finalData = sendQExceededMessage
275
 	}
277
 	}
276
 	socket.Unlock()
278
 	socket.Unlock()
277
 
279
 
279
 		return
281
 		return
280
 	}
282
 	}
281
 
283
 
282
-	if finalData != "" {
283
-		socket.conn.Write([]byte(finalData))
284
+	if len(finalData) != 0 {
285
+		socket.conn.Write(finalData)
284
 	}
286
 	}
285
 
287
 
286
 	// close the connection
288
 	// close the connection

+ 0
- 3
irc/types.go View File

6
 package irc
6
 package irc
7
 
7
 
8
 import "github.com/oragono/oragono/irc/modes"
8
 import "github.com/oragono/oragono/irc/modes"
9
-import "github.com/goshuirc/irc-go/ircmsg"
10
 
9
 
11
 // ClientSet is a set of clients.
10
 // ClientSet is a set of clients.
12
 type ClientSet map[*Client]bool
11
 type ClientSet map[*Client]bool
57
 
56
 
58
 // ChannelSet is a set of channels.
57
 // ChannelSet is a set of channels.
59
 type ChannelSet map[*Channel]bool
58
 type ChannelSet map[*Channel]bool
60
-
61
-type Tags *map[string]ircmsg.TagValue

+ 0
- 27
irc/utils/message_tags.go View File

1
-// Copyright (c) 2016-2017 Daniel Oaks <daniel@danieloaks.net>
2
-// released under the MIT license
3
-
4
-package utils
5
-
6
-import "github.com/goshuirc/irc-go/ircmsg"
7
-
8
-// GetClientOnlyTags takes a tag map and returns a map containing just the client-only tags from it.
9
-func GetClientOnlyTags(tags map[string]ircmsg.TagValue) *map[string]ircmsg.TagValue {
10
-	if len(tags) < 1 {
11
-		return nil
12
-	}
13
-
14
-	clientOnlyTags := make(map[string]ircmsg.TagValue)
15
-
16
-	for name, value := range tags {
17
-		if len(name) > 1 && name[0] == '+' {
18
-			clientOnlyTags[name] = value
19
-		}
20
-	}
21
-
22
-	if len(clientOnlyTags) < 1 {
23
-		return nil
24
-	}
25
-
26
-	return &clientOnlyTags
27
-}

+ 20
- 5
irc/utils/text.go View File

50
 	return lines
50
 	return lines
51
 }
51
 }
52
 
52
 
53
+type MessagePair struct {
54
+	Message string
55
+	Msgid   string
56
+}
57
+
53
 // SplitMessage represents a message that's been split for sending.
58
 // SplitMessage represents a message that's been split for sending.
54
 type SplitMessage struct {
59
 type SplitMessage struct {
55
-	Original string
56
-	Wrapped  []string // if this is nil, Original didn't need wrapping and can be sent to anyone
60
+	MessagePair
61
+	Wrapped []MessagePair // if this is nil, `Message` didn't need wrapping and can be sent to anyone
57
 }
62
 }
58
 
63
 
64
+const defaultLineWidth = 400
65
+
59
 func MakeSplitMessage(original string, origIs512 bool) (result SplitMessage) {
66
 func MakeSplitMessage(original string, origIs512 bool) (result SplitMessage) {
60
-	result.Original = original
67
+	result.Message = original
68
+	result.Msgid = GenerateSecretToken()
61
 
69
 
62
-	if !origIs512 {
63
-		result.Wrapped = WordWrap(original, 400)
70
+	if !origIs512 && defaultLineWidth < len(original) {
71
+		wrapped := WordWrap(original, defaultLineWidth)
72
+		result.Wrapped = make([]MessagePair, len(wrapped))
73
+		for i, wrappedMessage := range wrapped {
74
+			result.Wrapped[i] = MessagePair{
75
+				Message: wrappedMessage,
76
+				Msgid:   GenerateSecretToken(),
77
+			}
78
+		}
64
 	}
79
 	}
65
 
80
 
66
 	return
81
 	return

+ 2
- 4
oragono.yaml View File

487
     # maximum length of IRC lines
487
     # maximum length of IRC lines
488
     # this should generally be 1024-2048, and will only apply when negotiated by clients
488
     # this should generally be 1024-2048, and will only apply when negotiated by clients
489
     linelen:
489
     linelen:
490
-        # tags section
491
-        tags: 2048
492
-
493
-        # rest of the message
490
+        # ratified version of the message-tags cap fixes the max tag length at 8191 bytes
491
+        # configurable length for the rest of the message:
494
         rest: 2048
492
         rest: 2048
495
 
493
 
496
 # fakelag: prevents clients from spamming commands too rapidly
494
 # fakelag: prevents clients from spamming commands too rapidly

+ 1
- 1
vendor

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

Loading…
Cancel
Save