forked from External/ergo
fix #782 (bring vendor into the main tree)
This commit is contained in:
parent
702c7b1e7c
commit
d0aa7cc860
616 changed files with 359667 additions and 31 deletions
13
vendor/github.com/goshuirc/irc-go/LICENSE
generated
vendored
Normal file
13
vendor/github.com/goshuirc/irc-go/LICENSE
generated
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
Copyright (c) 2016-2017 Daniel Oaks
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
PERFORMANCE OF THIS SOFTWARE.
|
||||
86
vendor/github.com/goshuirc/irc-go/ircfmt/doc.go
generated
vendored
Normal file
86
vendor/github.com/goshuirc/irc-go/ircfmt/doc.go
generated
vendored
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
// written by Daniel Oaks <daniel@danieloaks.net>
|
||||
// released under the ISC license
|
||||
|
||||
/*
|
||||
Package ircfmt handles IRC formatting codes, escaping and unescaping.
|
||||
|
||||
This allows for a simpler representation of strings that contain colour codes,
|
||||
bold codes, and such, without having to write and handle raw bytes when
|
||||
assembling outgoing messages.
|
||||
|
||||
This lets you turn raw IRC messages into our escaped versions, and turn escaped
|
||||
versions back into raw messages suitable for sending on IRC connections. This
|
||||
is designed to be used on things like PRIVMSG / NOTICE commands, MOTD blocks,
|
||||
and such.
|
||||
|
||||
The escape character we use in this library is the dollar sign ("$"), along
|
||||
with the given escape characters:
|
||||
|
||||
--------------------------------
|
||||
Name | Escape | Raw
|
||||
--------------------------------
|
||||
Dollarsign | $$ | $
|
||||
Bold | $b | 0x02
|
||||
Colour | $c | 0x03
|
||||
Monospace | $m | 0x11
|
||||
Reverse Colour | $v | 0x16
|
||||
Italic | $i | 0x1d
|
||||
Strikethrough | $s | 0x1e
|
||||
Underscore | $u | 0x1f
|
||||
Reset | $r | 0x0f
|
||||
--------------------------------
|
||||
|
||||
Colours are escaped in a slightly different way, using the actual names of them
|
||||
rather than just the raw numbers.
|
||||
|
||||
In our escaped format, the colours for the fore and background are contained in
|
||||
square brackets after the colour ("$c") escape. For example:
|
||||
|
||||
Red foreground:
|
||||
Escaped: This is a $c[red]cool message!
|
||||
Raw: This is a 0x034cool message!
|
||||
|
||||
Blue foreground, green background:
|
||||
Escaped: This is a $c[blue,green]rad message!
|
||||
Raw: This is a 0x032,3rad message!
|
||||
|
||||
When assembling a raw message, we make sure to use the full colour code
|
||||
("02" vs just "2") when it could become confused due to numbers just after the
|
||||
colour escape code. For instance, lines like this will be unescaped correctly:
|
||||
|
||||
No number after colour escape:
|
||||
Escaped: This is a $c[red]cool message!
|
||||
Raw: This is a 0x034cool message!
|
||||
|
||||
Number after colour escape:
|
||||
Escaped: This is $c[blue]20% cooler!
|
||||
Raw: This is 0x030220% cooler
|
||||
|
||||
Here are the colour names and codes we recognise:
|
||||
|
||||
--------------------
|
||||
Code | Name
|
||||
--------------------
|
||||
00 | white
|
||||
01 | black
|
||||
02 | blue
|
||||
03 | green
|
||||
04 | red
|
||||
05 | brown
|
||||
06 | magenta
|
||||
07 | orange
|
||||
08 | yellow
|
||||
09 | light green
|
||||
10 | cyan
|
||||
11 | light cyan
|
||||
12 | light blue
|
||||
13 | pink
|
||||
14 | grey
|
||||
15 | light grey
|
||||
99 | default
|
||||
--------------------
|
||||
|
||||
These other colours aren't given names:
|
||||
https://modern.ircdocs.horse/formatting.html#colors-16-98
|
||||
*/
|
||||
package ircfmt
|
||||
330
vendor/github.com/goshuirc/irc-go/ircfmt/ircfmt.go
generated
vendored
Normal file
330
vendor/github.com/goshuirc/irc-go/ircfmt/ircfmt.go
generated
vendored
Normal file
|
|
@ -0,0 +1,330 @@
|
|||
// written by Daniel Oaks <daniel@danieloaks.net>
|
||||
// released under the ISC license
|
||||
|
||||
package ircfmt
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
// raw bytes and strings to do replacing with
|
||||
bold string = "\x02"
|
||||
colour string = "\x03"
|
||||
monospace string = "\x11"
|
||||
reverseColour string = "\x16"
|
||||
italic string = "\x1d"
|
||||
strikethrough string = "\x1e"
|
||||
underline string = "\x1f"
|
||||
reset string = "\x0f"
|
||||
|
||||
runecolour rune = '\x03'
|
||||
runebold rune = '\x02'
|
||||
runemonospace rune = '\x11'
|
||||
runereverseColour rune = '\x16'
|
||||
runeitalic rune = '\x1d'
|
||||
runestrikethrough rune = '\x1e'
|
||||
runereset rune = '\x0f'
|
||||
runeunderline rune = '\x1f'
|
||||
|
||||
// valid characters in a colour code character, for speed
|
||||
colours1 string = "0123456789"
|
||||
)
|
||||
|
||||
var (
|
||||
// valtoescape replaces most of IRC characters with our escapes.
|
||||
valtoescape = strings.NewReplacer("$", "$$", colour, "$c", reverseColour, "$v", bold, "$b", italic, "$i", strikethrough, "$s", underline, "$u", monospace, "$m", reset, "$r")
|
||||
// valToStrip replaces most of the IRC characters with nothing
|
||||
valToStrip = strings.NewReplacer(colour, "$c", reverseColour, "", bold, "", italic, "", strikethrough, "", underline, "", monospace, "", reset, "")
|
||||
|
||||
// escapetoval contains most of our escapes and how they map to real IRC characters.
|
||||
// intentionally skips colour, since that's handled elsewhere.
|
||||
escapetoval = map[rune]string{
|
||||
'$': "$",
|
||||
'b': bold,
|
||||
'i': italic,
|
||||
'v': reverseColour,
|
||||
's': strikethrough,
|
||||
'u': underline,
|
||||
'm': monospace,
|
||||
'r': reset,
|
||||
}
|
||||
|
||||
// valid colour codes
|
||||
numtocolour = map[string]string{
|
||||
"99": "default",
|
||||
"15": "light grey",
|
||||
"14": "grey",
|
||||
"13": "pink",
|
||||
"12": "light blue",
|
||||
"11": "light cyan",
|
||||
"10": "cyan",
|
||||
"09": "light green",
|
||||
"08": "yellow",
|
||||
"07": "orange",
|
||||
"06": "magenta",
|
||||
"05": "brown",
|
||||
"04": "red",
|
||||
"03": "green",
|
||||
"02": "blue",
|
||||
"01": "black",
|
||||
"00": "white",
|
||||
"9": "light green",
|
||||
"8": "yellow",
|
||||
"7": "orange",
|
||||
"6": "magenta",
|
||||
"5": "brown",
|
||||
"4": "red",
|
||||
"3": "green",
|
||||
"2": "blue",
|
||||
"1": "black",
|
||||
"0": "white",
|
||||
}
|
||||
|
||||
// full and truncated colour codes
|
||||
colourcodesFull = map[string]string{
|
||||
"white": "00",
|
||||
"black": "01",
|
||||
"blue": "02",
|
||||
"green": "03",
|
||||
"red": "04",
|
||||
"brown": "05",
|
||||
"magenta": "06",
|
||||
"orange": "07",
|
||||
"yellow": "08",
|
||||
"light green": "09",
|
||||
"cyan": "10",
|
||||
"light cyan": "11",
|
||||
"light blue": "12",
|
||||
"pink": "13",
|
||||
"grey": "14",
|
||||
"light grey": "15",
|
||||
"default": "99",
|
||||
}
|
||||
colourcodesTruncated = map[string]string{
|
||||
"white": "0",
|
||||
"black": "1",
|
||||
"blue": "2",
|
||||
"green": "3",
|
||||
"red": "4",
|
||||
"brown": "5",
|
||||
"magenta": "6",
|
||||
"orange": "7",
|
||||
"yellow": "8",
|
||||
"light green": "9",
|
||||
"cyan": "10",
|
||||
"light cyan": "11",
|
||||
"light blue": "12",
|
||||
"pink": "13",
|
||||
"grey": "14",
|
||||
"light grey": "15",
|
||||
"default": "99",
|
||||
}
|
||||
)
|
||||
|
||||
// Escape takes a raw IRC string and returns it with our escapes.
|
||||
//
|
||||
// IE, it turns this: "This is a \x02cool\x02, \x034red\x0f message!"
|
||||
// into: "This is a $bcool$b, $c[red]red$r message!"
|
||||
func Escape(in string) string {
|
||||
// replace all our usual escapes
|
||||
in = valtoescape.Replace(in)
|
||||
|
||||
inRunes := []rune(in)
|
||||
//var out string
|
||||
out := strings.Builder{}
|
||||
for 0 < len(inRunes) {
|
||||
if 1 < len(inRunes) && inRunes[0] == '$' && inRunes[1] == 'c' {
|
||||
// handle colours
|
||||
out.WriteString("$c")
|
||||
inRunes = inRunes[2:] // strip colour code chars
|
||||
|
||||
if len(inRunes) < 1 || !strings.Contains(colours1, string(inRunes[0])) {
|
||||
out.WriteString("[]")
|
||||
continue
|
||||
}
|
||||
|
||||
var foreBuffer, backBuffer string
|
||||
foreBuffer += string(inRunes[0])
|
||||
inRunes = inRunes[1:]
|
||||
if 0 < len(inRunes) && strings.Contains(colours1, string(inRunes[0])) {
|
||||
foreBuffer += string(inRunes[0])
|
||||
inRunes = inRunes[1:]
|
||||
}
|
||||
if 1 < len(inRunes) && inRunes[0] == ',' && strings.Contains(colours1, string(inRunes[1])) {
|
||||
backBuffer += string(inRunes[1])
|
||||
inRunes = inRunes[2:]
|
||||
if 0 < len(inRunes) && strings.Contains(colours1, string(inRunes[0])) {
|
||||
backBuffer += string(inRunes[0])
|
||||
inRunes = inRunes[1:]
|
||||
}
|
||||
}
|
||||
|
||||
foreName, exists := numtocolour[foreBuffer]
|
||||
if !exists {
|
||||
foreName = foreBuffer
|
||||
}
|
||||
backName, exists := numtocolour[backBuffer]
|
||||
if !exists {
|
||||
backName = backBuffer
|
||||
}
|
||||
|
||||
out.WriteRune('[')
|
||||
out.WriteString(foreName)
|
||||
if backName != "" {
|
||||
out.WriteRune(',')
|
||||
out.WriteString(backName)
|
||||
}
|
||||
out.WriteRune(']')
|
||||
|
||||
} else {
|
||||
// special case for $$c
|
||||
if len(inRunes) > 2 && inRunes[0] == '$' && inRunes[1] == '$' && inRunes[2] == 'c' {
|
||||
out.WriteRune(inRunes[0])
|
||||
out.WriteRune(inRunes[1])
|
||||
out.WriteRune(inRunes[2])
|
||||
inRunes = inRunes[3:]
|
||||
} else {
|
||||
out.WriteRune(inRunes[0])
|
||||
inRunes = inRunes[1:]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return out.String()
|
||||
}
|
||||
|
||||
// Strip takes a raw IRC string and removes it with all formatting codes removed
|
||||
// IE, it turns this: "This is a \x02cool\x02, \x034red\x0f message!"
|
||||
// into: "This is a cool, red message!"
|
||||
func Strip(in string) string {
|
||||
out := strings.Builder{}
|
||||
runes := []rune(in)
|
||||
if out.Len() < len(runes) { // Reduce allocations where needed
|
||||
out.Grow(len(in) - out.Len())
|
||||
}
|
||||
for len(runes) > 0 {
|
||||
switch runes[0] {
|
||||
case runebold, runemonospace, runereverseColour, runeitalic, runestrikethrough, runeunderline, runereset:
|
||||
runes = runes[1:]
|
||||
case runecolour:
|
||||
runes = removeColour(runes)
|
||||
default:
|
||||
out.WriteRune(runes[0])
|
||||
runes = runes[1:]
|
||||
}
|
||||
}
|
||||
return out.String()
|
||||
}
|
||||
|
||||
func removeNumber(runes []rune) []rune {
|
||||
if len(runes) > 0 && runes[0] >= '0' && runes[0] <= '9' {
|
||||
runes = runes[1:]
|
||||
}
|
||||
return runes
|
||||
}
|
||||
|
||||
func removeColour(runes []rune) []rune {
|
||||
if runes[0] != runecolour {
|
||||
return runes
|
||||
}
|
||||
|
||||
runes = runes[1:]
|
||||
runes = removeNumber(runes)
|
||||
runes = removeNumber(runes)
|
||||
|
||||
if len(runes) > 1 && runes[0] == ',' && runes[1] >= '0' && runes[1] <= '9' {
|
||||
runes = runes[2:]
|
||||
} else {
|
||||
return runes // Nothing else because we dont have a comma
|
||||
}
|
||||
runes = removeNumber(runes)
|
||||
return runes
|
||||
}
|
||||
|
||||
// Unescape takes our escaped string and returns a raw IRC string.
|
||||
//
|
||||
// IE, it turns this: "This is a $bcool$b, $c[red]red$r message!"
|
||||
// into this: "This is a \x02cool\x02, \x034red\x0f message!"
|
||||
func Unescape(in string) string {
|
||||
out := strings.Builder{}
|
||||
|
||||
remaining := []rune(in)
|
||||
for 0 < len(remaining) {
|
||||
char := remaining[0]
|
||||
remaining = remaining[1:]
|
||||
|
||||
if char == '$' && 0 < len(remaining) {
|
||||
char = remaining[0]
|
||||
remaining = remaining[1:]
|
||||
|
||||
val, exists := escapetoval[char]
|
||||
if exists {
|
||||
out.WriteString(val)
|
||||
} else if char == 'c' {
|
||||
out.WriteString(colour)
|
||||
|
||||
if len(remaining) < 2 || remaining[0] != '[' {
|
||||
continue
|
||||
}
|
||||
|
||||
// get colour names
|
||||
var coloursBuffer string
|
||||
remaining = remaining[1:]
|
||||
for remaining[0] != ']' {
|
||||
coloursBuffer += string(remaining[0])
|
||||
remaining = remaining[1:]
|
||||
}
|
||||
remaining = remaining[1:] // strip final ']'
|
||||
|
||||
colours := strings.Split(coloursBuffer, ",")
|
||||
var foreColour, backColour string
|
||||
foreColour = colours[0]
|
||||
if 1 < len(colours) {
|
||||
backColour = colours[1]
|
||||
}
|
||||
|
||||
// decide whether we can use truncated colour codes
|
||||
canUseTruncated := len(remaining) < 1 || !strings.Contains(colours1, string(remaining[0]))
|
||||
|
||||
// turn colour names into real codes
|
||||
var foreColourCode, backColourCode string
|
||||
var exists bool
|
||||
|
||||
if backColour != "" || canUseTruncated {
|
||||
foreColourCode, exists = colourcodesTruncated[foreColour]
|
||||
} else {
|
||||
foreColourCode, exists = colourcodesFull[foreColour]
|
||||
}
|
||||
if exists {
|
||||
foreColour = foreColourCode
|
||||
}
|
||||
|
||||
if backColour != "" {
|
||||
if canUseTruncated {
|
||||
backColourCode, exists = colourcodesTruncated[backColour]
|
||||
} else {
|
||||
backColourCode, exists = colourcodesFull[backColour]
|
||||
}
|
||||
if exists {
|
||||
backColour = backColourCode
|
||||
}
|
||||
}
|
||||
|
||||
// output colour codes
|
||||
out.WriteString(foreColour)
|
||||
if backColour != "" {
|
||||
out.WriteRune(',')
|
||||
out.WriteString(backColour)
|
||||
}
|
||||
} else {
|
||||
// unknown char
|
||||
out.WriteRune(char)
|
||||
}
|
||||
} else {
|
||||
out.WriteRune(char)
|
||||
}
|
||||
}
|
||||
|
||||
return out.String()
|
||||
}
|
||||
7
vendor/github.com/goshuirc/irc-go/ircmatch/doc.go
generated
vendored
Normal file
7
vendor/github.com/goshuirc/irc-go/ircmatch/doc.go
generated
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
// written by Daniel Oaks <daniel@danieloaks.net>
|
||||
// released under the ISC license
|
||||
|
||||
/*
|
||||
Package ircmatch handles matching IRC strings with the traditional glob-like syntax.
|
||||
*/
|
||||
package ircmatch
|
||||
57
vendor/github.com/goshuirc/irc-go/ircmatch/ircmatch.go
generated
vendored
Normal file
57
vendor/github.com/goshuirc/irc-go/ircmatch/ircmatch.go
generated
vendored
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
package ircmatch
|
||||
|
||||
import enfa "github.com/goshuirc/e-nfa"
|
||||
|
||||
// Matcher represents an object that can match IRC strings.
|
||||
type Matcher struct {
|
||||
internalENFA *enfa.ENFA
|
||||
}
|
||||
|
||||
// MakeMatch creates a Matcher.
|
||||
func MakeMatch(globTemplate string) Matcher {
|
||||
var newmatch Matcher
|
||||
|
||||
// assemble internal enfa
|
||||
newmatch.internalENFA = enfa.NewENFA(0, false)
|
||||
|
||||
var currentState int
|
||||
var lastWasStar bool
|
||||
for _, char := range globTemplate {
|
||||
if char == '*' {
|
||||
if lastWasStar {
|
||||
continue
|
||||
}
|
||||
newmatch.internalENFA.AddTransition(currentState, "*", currentState)
|
||||
lastWasStar = true
|
||||
continue
|
||||
} else if char == '?' {
|
||||
newmatch.internalENFA.AddState(currentState+1, false)
|
||||
newmatch.internalENFA.AddTransition(currentState, "?", currentState+1)
|
||||
currentState++
|
||||
} else {
|
||||
newmatch.internalENFA.AddState(currentState+1, false)
|
||||
newmatch.internalENFA.AddTransition(currentState, string(char), currentState+1)
|
||||
currentState++
|
||||
}
|
||||
|
||||
lastWasStar = false
|
||||
}
|
||||
|
||||
// create end state
|
||||
newmatch.internalENFA.AddState(currentState+1, true)
|
||||
newmatch.internalENFA.AddTransition(currentState, "", currentState+1)
|
||||
|
||||
return newmatch
|
||||
}
|
||||
|
||||
// Match returns true if the given string matches this glob.
|
||||
func (menfa *Matcher) Match(search string) bool {
|
||||
var searchChars []string
|
||||
for _, char := range search {
|
||||
searchChars = append(searchChars, string(char))
|
||||
}
|
||||
|
||||
isMatch := menfa.internalENFA.VerifyInputs(searchChars)
|
||||
menfa.internalENFA.Reset()
|
||||
return isMatch
|
||||
}
|
||||
7
vendor/github.com/goshuirc/irc-go/ircmsg/doc.go
generated
vendored
Normal file
7
vendor/github.com/goshuirc/irc-go/ircmsg/doc.go
generated
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
// written by Daniel Oaks <daniel@danieloaks.net>
|
||||
// released under the ISC license
|
||||
|
||||
/*
|
||||
Package ircmsg helps parse and create lines for IRC connections.
|
||||
*/
|
||||
package ircmsg
|
||||
401
vendor/github.com/goshuirc/irc-go/ircmsg/message.go
generated
vendored
Normal file
401
vendor/github.com/goshuirc/irc-go/ircmsg/message.go
generated
vendored
Normal file
|
|
@ -0,0 +1,401 @@
|
|||
// Copyright (c) 2016-2019 Daniel Oaks <daniel@danieloaks.net>
|
||||
// Copyright (c) 2018-2019 Shivaram Lingamneni <slingamn@cs.stanford.edu>
|
||||
|
||||
// released under the ISC license
|
||||
|
||||
package ircmsg
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
// "The size limit for message tags is 8191 bytes, including the leading
|
||||
// '@' (0x40) and trailing space ' ' (0x20) characters."
|
||||
MaxlenTags = 8191
|
||||
|
||||
// MaxlenTags - ('@' + ' ')
|
||||
MaxlenTagData = MaxlenTags - 2
|
||||
|
||||
// "Clients MUST NOT send messages with tag data exceeding 4094 bytes,
|
||||
// this includes tags with or without the client-only prefix."
|
||||
MaxlenClientTagData = 4094
|
||||
|
||||
// "Servers MUST NOT add tag data exceeding 4094 bytes to messages."
|
||||
MaxlenServerTagData = 4094
|
||||
|
||||
// '@' + MaxlenClientTagData + ' '
|
||||
// this is the analogue of MaxlenTags when the source of the message is a client
|
||||
MaxlenTagsFromClient = MaxlenClientTagData + 2
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrorLineIsEmpty indicates that the given IRC line was empty.
|
||||
ErrorLineIsEmpty = errors.New("Line is empty")
|
||||
// ErrorLineContainsBadChar indicates that the line contained invalid characters
|
||||
ErrorLineContainsBadChar = errors.New("Line contains invalid characters")
|
||||
// ErrorLineTooLong indicates that the message exceeded the maximum tag length
|
||||
// (the name references 417 ERR_INPUTTOOLONG; we reserve the right to return it
|
||||
// for messages that exceed the non-tag length limit)
|
||||
ErrorLineTooLong = errors.New("Line could not be parsed because a specified length limit was exceeded")
|
||||
|
||||
ErrorCommandMissing = errors.New("IRC messages MUST have a command")
|
||||
ErrorBadParam = errors.New("Cannot have an empty param, a param with spaces, or a param that starts with ':' before the last parameter")
|
||||
)
|
||||
|
||||
// IrcMessage represents an IRC message, as defined by the RFCs and as
|
||||
// extended by the IRCv3 Message Tags specification with the introduction
|
||||
// of message tags.
|
||||
type IrcMessage struct {
|
||||
Prefix string
|
||||
Command string
|
||||
Params []string
|
||||
tags map[string]string
|
||||
clientOnlyTags map[string]string
|
||||
}
|
||||
|
||||
// GetTag returns whether a tag is present, and if so, what its value is.
|
||||
func (msg *IrcMessage) GetTag(tagName string) (present bool, value string) {
|
||||
if len(tagName) == 0 {
|
||||
return
|
||||
} else if tagName[0] == '+' {
|
||||
value, present = msg.clientOnlyTags[tagName]
|
||||
return
|
||||
} else {
|
||||
value, present = msg.tags[tagName]
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// HasTag returns whether a tag is present.
|
||||
func (msg *IrcMessage) HasTag(tagName string) (present bool) {
|
||||
present, _ = msg.GetTag(tagName)
|
||||
return
|
||||
}
|
||||
|
||||
// SetTag sets a tag.
|
||||
func (msg *IrcMessage) SetTag(tagName, tagValue string) {
|
||||
if len(tagName) == 0 {
|
||||
return
|
||||
} else if tagName[0] == '+' {
|
||||
if msg.clientOnlyTags == nil {
|
||||
msg.clientOnlyTags = make(map[string]string)
|
||||
}
|
||||
msg.clientOnlyTags[tagName] = tagValue
|
||||
} else {
|
||||
if msg.tags == nil {
|
||||
msg.tags = make(map[string]string)
|
||||
}
|
||||
msg.tags[tagName] = tagValue
|
||||
}
|
||||
}
|
||||
|
||||
// DeleteTag deletes a tag.
|
||||
func (msg *IrcMessage) DeleteTag(tagName string) {
|
||||
if len(tagName) == 0 {
|
||||
return
|
||||
} else if tagName[0] == '+' {
|
||||
delete(msg.clientOnlyTags, tagName)
|
||||
} else {
|
||||
delete(msg.tags, tagName)
|
||||
}
|
||||
}
|
||||
|
||||
// UpdateTags is a convenience to set multiple tags at once.
|
||||
func (msg *IrcMessage) UpdateTags(tags map[string]string) {
|
||||
for name, value := range tags {
|
||||
msg.SetTag(name, value)
|
||||
}
|
||||
}
|
||||
|
||||
// AllTags returns all tags as a single map.
|
||||
func (msg *IrcMessage) AllTags() (result map[string]string) {
|
||||
result = make(map[string]string, len(msg.tags)+len(msg.clientOnlyTags))
|
||||
for name, value := range msg.tags {
|
||||
result[name] = value
|
||||
}
|
||||
for name, value := range msg.clientOnlyTags {
|
||||
result[name] = value
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ClientOnlyTags returns the client-only tags (the tags with the + prefix).
|
||||
// The returned map may be internal storage of the IrcMessage object and
|
||||
// should not be modified.
|
||||
func (msg *IrcMessage) ClientOnlyTags() map[string]string {
|
||||
return msg.clientOnlyTags
|
||||
}
|
||||
|
||||
// ParseLine creates and returns a message from the given IRC line.
|
||||
func ParseLine(line string) (ircmsg IrcMessage, err error) {
|
||||
return parseLine(line, 0, 0)
|
||||
}
|
||||
|
||||
// ParseLineStrict creates and returns an IrcMessage from the given IRC line,
|
||||
// taking the maximum length into account and truncating the message as appropriate.
|
||||
// If fromClient is true, it enforces the client limit on tag data length (4094 bytes),
|
||||
// allowing the server to return ERR_INPUTTOOLONG as appropriate. If truncateLen is
|
||||
// nonzero, it is the length at which the non-tag portion of the message is truncated.
|
||||
func ParseLineStrict(line string, fromClient bool, truncateLen int) (ircmsg IrcMessage, err error) {
|
||||
maxTagDataLength := MaxlenTagData
|
||||
if fromClient {
|
||||
maxTagDataLength = MaxlenClientTagData
|
||||
}
|
||||
return parseLine(line, maxTagDataLength, truncateLen)
|
||||
}
|
||||
|
||||
// slice off any amount of ' ' from the front of the string
|
||||
func trimInitialSpaces(str string) string {
|
||||
var i int
|
||||
for i = 0; i < len(str) && str[i] == ' '; i += 1 {
|
||||
}
|
||||
return str[i:]
|
||||
}
|
||||
|
||||
func parseLine(line string, maxTagDataLength int, truncateLen int) (ircmsg IrcMessage, err error) {
|
||||
if strings.IndexByte(line, '\x00') != -1 {
|
||||
err = ErrorLineContainsBadChar
|
||||
return
|
||||
}
|
||||
|
||||
// trim to the first appearance of either '\r' or '\n':
|
||||
lineEnd := strings.IndexByte(line, '\r')
|
||||
newlineIndex := strings.IndexByte(line, '\n')
|
||||
if newlineIndex != -1 && (lineEnd == -1 || newlineIndex < lineEnd) {
|
||||
lineEnd = newlineIndex
|
||||
}
|
||||
if lineEnd != -1 {
|
||||
line = line[:lineEnd]
|
||||
}
|
||||
|
||||
if len(line) < 1 {
|
||||
return ircmsg, ErrorLineIsEmpty
|
||||
}
|
||||
|
||||
// tags
|
||||
if line[0] == '@' {
|
||||
tagEnd := strings.IndexByte(line, ' ')
|
||||
if tagEnd == -1 {
|
||||
return ircmsg, ErrorLineIsEmpty
|
||||
}
|
||||
tags := line[1:tagEnd]
|
||||
if 0 < maxTagDataLength && maxTagDataLength < len(tags) {
|
||||
return ircmsg, ErrorLineTooLong
|
||||
}
|
||||
err = ircmsg.parseTags(tags)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// skip over the tags and the separating space
|
||||
line = line[tagEnd+1:]
|
||||
}
|
||||
|
||||
// truncate if desired
|
||||
if 0 < truncateLen && truncateLen < len(line) {
|
||||
line = line[:truncateLen]
|
||||
}
|
||||
|
||||
// modern: "These message parts, and parameters themselves, are separated
|
||||
// by one or more ASCII SPACE characters"
|
||||
line = trimInitialSpaces(line)
|
||||
|
||||
// prefix
|
||||
if 0 < len(line) && line[0] == ':' {
|
||||
prefixEnd := strings.IndexByte(line, ' ')
|
||||
if prefixEnd == -1 {
|
||||
return ircmsg, ErrorLineIsEmpty
|
||||
}
|
||||
ircmsg.Prefix = line[1:prefixEnd]
|
||||
// skip over the prefix and the separating space
|
||||
line = line[prefixEnd+1:]
|
||||
}
|
||||
|
||||
line = trimInitialSpaces(line)
|
||||
|
||||
// command
|
||||
commandEnd := strings.IndexByte(line, ' ')
|
||||
paramStart := commandEnd + 1
|
||||
if commandEnd == -1 {
|
||||
commandEnd = len(line)
|
||||
paramStart = len(line)
|
||||
}
|
||||
// normalize command to uppercase:
|
||||
ircmsg.Command = strings.ToUpper(line[:commandEnd])
|
||||
if len(ircmsg.Command) == 0 {
|
||||
return ircmsg, ErrorLineIsEmpty
|
||||
}
|
||||
line = line[paramStart:]
|
||||
|
||||
for {
|
||||
line = trimInitialSpaces(line)
|
||||
if len(line) == 0 {
|
||||
break
|
||||
}
|
||||
// handle trailing
|
||||
if line[0] == ':' {
|
||||
ircmsg.Params = append(ircmsg.Params, line[1:])
|
||||
break
|
||||
}
|
||||
paramEnd := strings.IndexByte(line, ' ')
|
||||
if paramEnd == -1 {
|
||||
ircmsg.Params = append(ircmsg.Params, line)
|
||||
break
|
||||
}
|
||||
ircmsg.Params = append(ircmsg.Params, line[:paramEnd])
|
||||
line = line[paramEnd+1:]
|
||||
}
|
||||
|
||||
return ircmsg, nil
|
||||
}
|
||||
|
||||
// helper to parse tags
|
||||
func (ircmsg *IrcMessage) parseTags(tags string) (err error) {
|
||||
for 0 < len(tags) {
|
||||
tagEnd := strings.IndexByte(tags, ';')
|
||||
endPos := tagEnd
|
||||
nextPos := tagEnd + 1
|
||||
if tagEnd == -1 {
|
||||
endPos = len(tags)
|
||||
nextPos = len(tags)
|
||||
}
|
||||
tagPair := tags[:endPos]
|
||||
equalsIndex := strings.IndexByte(tagPair, '=')
|
||||
var tagName, tagValue string
|
||||
if equalsIndex == -1 {
|
||||
// tag with no value
|
||||
tagName = tagPair
|
||||
} else {
|
||||
tagName, tagValue = tagPair[:equalsIndex], tagPair[equalsIndex+1:]
|
||||
}
|
||||
ircmsg.SetTag(tagName, UnescapeTagValue(tagValue))
|
||||
// skip over the tag just processed, plus the delimiting ; if any
|
||||
tags = tags[nextPos:]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MakeMessage provides a simple way to create a new IrcMessage.
|
||||
func MakeMessage(tags map[string]string, prefix string, command string, params ...string) (ircmsg IrcMessage) {
|
||||
ircmsg.Prefix = prefix
|
||||
ircmsg.Command = command
|
||||
ircmsg.Params = params
|
||||
ircmsg.UpdateTags(tags)
|
||||
return ircmsg
|
||||
}
|
||||
|
||||
// Line returns a sendable line created from an IrcMessage.
|
||||
func (ircmsg *IrcMessage) Line() (result string, err error) {
|
||||
bytes, err := ircmsg.line(0, 0, 0, 0)
|
||||
if err == nil {
|
||||
result = string(bytes)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// LineBytes returns a sendable line created from an IrcMessage.
|
||||
func (ircmsg *IrcMessage) LineBytes() (result []byte, err error) {
|
||||
result, err = ircmsg.line(0, 0, 0, 0)
|
||||
return
|
||||
}
|
||||
|
||||
// LineBytesStrict returns a sendable line, as a []byte, created from an IrcMessage.
|
||||
// fromClient controls whether the server-side or client-side tag length limit
|
||||
// is enforced. If truncateLen is nonzero, it is the length at which the
|
||||
// non-tag portion of the message is truncated.
|
||||
func (ircmsg *IrcMessage) LineBytesStrict(fromClient bool, truncateLen int) ([]byte, error) {
|
||||
var tagLimit, clientOnlyTagDataLimit, serverAddedTagDataLimit int
|
||||
if fromClient {
|
||||
// enforce client max tags:
|
||||
// <client_max> (4096) :: '@' <tag_data 4094> ' '
|
||||
tagLimit = MaxlenTagsFromClient
|
||||
} else {
|
||||
// on the server side, enforce separate client-only and server-added tag budgets:
|
||||
// "Servers MUST NOT add tag data exceeding 4094 bytes to messages."
|
||||
// <combined_max> (8191) :: '@' <tag_data 4094> ';' <tag_data 4094> ' '
|
||||
clientOnlyTagDataLimit = MaxlenClientTagData
|
||||
serverAddedTagDataLimit = MaxlenServerTagData
|
||||
}
|
||||
return ircmsg.line(tagLimit, clientOnlyTagDataLimit, serverAddedTagDataLimit, truncateLen)
|
||||
}
|
||||
|
||||
// line returns a sendable line created from an IrcMessage.
|
||||
func (ircmsg *IrcMessage) line(tagLimit, clientOnlyTagDataLimit, serverAddedTagDataLimit, truncateLen int) ([]byte, error) {
|
||||
if len(ircmsg.Command) < 1 {
|
||||
return nil, ErrorCommandMissing
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
|
||||
// write the tags, computing the budgets for client-only tags and regular tags
|
||||
var lenRegularTags, lenClientOnlyTags, lenTags int
|
||||
if 0 < len(ircmsg.tags) || 0 < len(ircmsg.clientOnlyTags) {
|
||||
buf.WriteByte('@')
|
||||
firstTag := true
|
||||
writeTags := func(tags map[string]string) {
|
||||
for tag, val := range tags {
|
||||
if !firstTag {
|
||||
buf.WriteByte(';') // delimiter
|
||||
}
|
||||
buf.WriteString(tag)
|
||||
if val != "" {
|
||||
buf.WriteByte('=')
|
||||
buf.WriteString(EscapeTagValue(val))
|
||||
}
|
||||
firstTag = false
|
||||
}
|
||||
}
|
||||
writeTags(ircmsg.tags)
|
||||
lenRegularTags = buf.Len() - 1 // '@' is not counted
|
||||
writeTags(ircmsg.clientOnlyTags)
|
||||
lenClientOnlyTags = (buf.Len() - 1) - lenRegularTags // '@' is not counted
|
||||
if lenRegularTags != 0 {
|
||||
// semicolon between regular and client-only tags is not counted
|
||||
lenClientOnlyTags -= 1
|
||||
}
|
||||
buf.WriteByte(' ')
|
||||
}
|
||||
lenTags = buf.Len()
|
||||
|
||||
if 0 < tagLimit && tagLimit < buf.Len() {
|
||||
return nil, ErrorLineTooLong
|
||||
}
|
||||
if (0 < clientOnlyTagDataLimit && clientOnlyTagDataLimit < lenClientOnlyTags) || (0 < serverAddedTagDataLimit && serverAddedTagDataLimit < lenRegularTags) {
|
||||
return nil, ErrorLineTooLong
|
||||
}
|
||||
|
||||
if len(ircmsg.Prefix) > 0 {
|
||||
buf.WriteByte(':')
|
||||
buf.WriteString(ircmsg.Prefix)
|
||||
buf.WriteByte(' ')
|
||||
}
|
||||
|
||||
buf.WriteString(ircmsg.Command)
|
||||
|
||||
for i, param := range ircmsg.Params {
|
||||
buf.WriteByte(' ')
|
||||
if len(param) < 1 || strings.IndexByte(param, ' ') != -1 || param[0] == ':' {
|
||||
if i != len(ircmsg.Params)-1 {
|
||||
return nil, ErrorBadParam
|
||||
}
|
||||
buf.WriteByte(':')
|
||||
}
|
||||
buf.WriteString(param)
|
||||
}
|
||||
|
||||
// truncate if desired
|
||||
// -2 for \r\n
|
||||
restLen := buf.Len() - lenTags
|
||||
if 0 < truncateLen && (truncateLen-2) < restLen {
|
||||
buf.Truncate(lenTags + (truncateLen - 2))
|
||||
}
|
||||
buf.WriteString("\r\n")
|
||||
|
||||
result := buf.Bytes()
|
||||
if bytes.IndexByte(result, '\x00') != -1 {
|
||||
return nil, ErrorLineContainsBadChar
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
75
vendor/github.com/goshuirc/irc-go/ircmsg/tags.go
generated
vendored
Normal file
75
vendor/github.com/goshuirc/irc-go/ircmsg/tags.go
generated
vendored
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
// written by Daniel Oaks <daniel@danieloaks.net>
|
||||
// released under the ISC license
|
||||
|
||||
package ircmsg
|
||||
|
||||
import "bytes"
|
||||
import "strings"
|
||||
|
||||
var (
|
||||
// valtoescape replaces real characters with message tag escapes.
|
||||
valtoescape = strings.NewReplacer("\\", "\\\\", ";", "\\:", " ", "\\s", "\r", "\\r", "\n", "\\n")
|
||||
|
||||
escapedCharLookupTable [256]byte
|
||||
)
|
||||
|
||||
func init() {
|
||||
// most chars escape to themselves
|
||||
for i := 0; i < 256; i += 1 {
|
||||
escapedCharLookupTable[i] = byte(i)
|
||||
}
|
||||
// these are the exceptions
|
||||
escapedCharLookupTable[':'] = ';'
|
||||
escapedCharLookupTable['s'] = ' '
|
||||
escapedCharLookupTable['r'] = '\r'
|
||||
escapedCharLookupTable['n'] = '\n'
|
||||
}
|
||||
|
||||
// EscapeTagValue takes a value, and returns an escaped message tag value.
|
||||
//
|
||||
// This function is automatically used when lines are created from an
|
||||
// IrcMessage, so you don't need to call it yourself before creating a line.
|
||||
func EscapeTagValue(inString string) string {
|
||||
return valtoescape.Replace(inString)
|
||||
}
|
||||
|
||||
// UnescapeTagValue takes an escaped message tag value, and returns the raw value.
|
||||
//
|
||||
// This function is automatically used when lines are interpreted by ParseLine,
|
||||
// so you don't need to call it yourself after parsing a line.
|
||||
func UnescapeTagValue(inString string) string {
|
||||
// buf.Len() == 0 is the fastpath where we have not needed to unescape any chars
|
||||
var buf bytes.Buffer
|
||||
remainder := inString
|
||||
for {
|
||||
backslashPos := strings.IndexByte(remainder, '\\')
|
||||
|
||||
if backslashPos == -1 {
|
||||
if buf.Len() == 0 {
|
||||
return inString
|
||||
} else {
|
||||
buf.WriteString(remainder)
|
||||
break
|
||||
}
|
||||
} else if backslashPos == len(remainder)-1 {
|
||||
// trailing backslash, which we strip
|
||||
if buf.Len() == 0 {
|
||||
return inString[:len(inString)-1]
|
||||
} else {
|
||||
buf.WriteString(remainder[:len(remainder)-1])
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// non-trailing backslash detected; we're now on the slowpath
|
||||
// where we modify the string
|
||||
if buf.Len() == 0 {
|
||||
buf.Grow(len(inString)) // just an optimization
|
||||
}
|
||||
buf.WriteString(remainder[:backslashPos])
|
||||
buf.WriteByte(escapedCharLookupTable[remainder[backslashPos+1]])
|
||||
remainder = remainder[backslashPos+2:]
|
||||
}
|
||||
|
||||
return buf.String()
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue