diff options
Diffstat (limited to 'vendor/github.com/sorcix/irc/message.go')
-rw-r--r-- | vendor/github.com/sorcix/irc/message.go | 308 |
1 files changed, 308 insertions, 0 deletions
diff --git a/vendor/github.com/sorcix/irc/message.go b/vendor/github.com/sorcix/irc/message.go new file mode 100644 index 00000000..088938dc --- /dev/null +++ b/vendor/github.com/sorcix/irc/message.go @@ -0,0 +1,308 @@ +// Copyright 2014 Vic Demuzere +// +// Use of this source code is governed by the MIT license. + +package irc + +import ( + "bytes" + "strings" +) + +// Various constants used for formatting IRC messages. +const ( + prefix byte = 0x3A // Prefix or last argument + prefixUser byte = 0x21 // Username + prefixHost byte = 0x40 // Hostname + space byte = 0x20 // Separator + + maxLength = 510 // Maximum length is 512 - 2 for the line endings. +) + +func cutsetFunc(r rune) bool { + // Characters to trim from prefixes/messages. + return r == '\r' || r == '\n' +} + +// Sender represents objects that are able to send messages to an IRC server. +// +// As there might be a message queue, it is possible that Send returns a nil +// error, but the message is not sent (yet). The error value is only used when +// it is certain that sending the message is impossible. +// +// This interface is not used inside this package, and shouldn't have been +// defined here in the first place. For backwards compatibility only. +type Sender interface { + Send(*Message) error +} + +// Prefix represents the prefix (sender) of an IRC message. +// See RFC1459 section 2.3.1. +// +// <servername> | <nick> [ '!' <user> ] [ '@' <host> ] +// +type Prefix struct { + Name string // Nick- or servername + User string // Username + Host string // Hostname +} + +// ParsePrefix takes a string and attempts to create a Prefix struct. +func ParsePrefix(raw string) (p *Prefix) { + + p = new(Prefix) + + user := indexByte(raw, prefixUser) + host := indexByte(raw, prefixHost) + + switch { + + case user > 0 && host > user: + p.Name = raw[:user] + p.User = raw[user+1 : host] + p.Host = raw[host+1:] + + case user > 0: + p.Name = raw[:user] + p.User = raw[user+1:] + + case host > 0: + p.Name = raw[:host] + p.Host = raw[host+1:] + + default: + p.Name = raw + + } + + return p +} + +// Len calculates the length of the string representation of this prefix. +func (p *Prefix) Len() (length int) { + length = len(p.Name) + if len(p.User) > 0 { + length = length + len(p.User) + 1 + } + if len(p.Host) > 0 { + length = length + len(p.Host) + 1 + } + return +} + +// Bytes returns a []byte representation of this prefix. +func (p *Prefix) Bytes() []byte { + buffer := new(bytes.Buffer) + p.writeTo(buffer) + return buffer.Bytes() +} + +// String returns a string representation of this prefix. +func (p *Prefix) String() (s string) { + // Benchmarks revealed that in this case simple string concatenation + // is actually faster than using a ByteBuffer as in (*Message).String() + s = p.Name + if len(p.User) > 0 { + s = s + string(prefixUser) + p.User + } + if len(p.Host) > 0 { + s = s + string(prefixHost) + p.Host + } + return +} + +// IsHostmask returns true if this prefix looks like a user hostmask. +func (p *Prefix) IsHostmask() bool { + return len(p.User) > 0 && len(p.Host) > 0 +} + +// IsServer returns true if this prefix looks like a server name. +func (p *Prefix) IsServer() bool { + return len(p.User) <= 0 && len(p.Host) <= 0 // && indexByte(p.Name, '.') > 0 +} + +// writeTo is an utility function to write the prefix to the bytes.Buffer in Message.String(). +func (p *Prefix) writeTo(buffer *bytes.Buffer) { + buffer.WriteString(p.Name) + if len(p.User) > 0 { + buffer.WriteByte(prefixUser) + buffer.WriteString(p.User) + } + if len(p.Host) > 0 { + buffer.WriteByte(prefixHost) + buffer.WriteString(p.Host) + } + return +} + +// Message represents an IRC protocol message. +// See RFC1459 section 2.3.1. +// +// <message> ::= [':' <prefix> <SPACE> ] <command> <params> <crlf> +// <prefix> ::= <servername> | <nick> [ '!' <user> ] [ '@' <host> ] +// <command> ::= <letter> { <letter> } | <number> <number> <number> +// <SPACE> ::= ' ' { ' ' } +// <params> ::= <SPACE> [ ':' <trailing> | <middle> <params> ] +// +// <middle> ::= <Any *non-empty* sequence of octets not including SPACE +// or NUL or CR or LF, the first of which may not be ':'> +// <trailing> ::= <Any, possibly *empty*, sequence of octets not including +// NUL or CR or LF> +// +// <crlf> ::= CR LF +type Message struct { + *Prefix + Command string + Params []string + Trailing string + + // When set to true, the trailing prefix (:) will be added even if the trailing message is empty. + EmptyTrailing bool +} + +// ParseMessage takes a string and attempts to create a Message struct. +// Returns nil if the Message is invalid. +func ParseMessage(raw string) (m *Message) { + + // Ignore empty messages. + if raw = strings.TrimFunc(raw, cutsetFunc); len(raw) < 2 { + return nil + } + + i, j := 0, 0 + + m = new(Message) + + if raw[0] == prefix { + + // Prefix ends with a space. + i = indexByte(raw, space) + + // Prefix string must not be empty if the indicator is present. + if i < 2 { + return nil + } + + m.Prefix = ParsePrefix(raw[1:i]) + + // Skip space at the end of the prefix + i++ + } + + // Find end of command + j = i + indexByte(raw[i:], space) + + // Extract command + if j > i { + m.Command = strings.ToUpper(raw[i:j]) + } else { + m.Command = strings.ToUpper(raw[i:]) + + // We're done here! + return m + } + + // Skip space after command + j++ + + // Find prefix for trailer + i = indexByte(raw[j:], prefix) + + if i < 0 || raw[j+i-1] != space { + + // There is no trailing argument! + m.Params = strings.Split(raw[j:], string(space)) + + // We're done here! + return m + } + + // Compensate for index on substring + i = i + j + + // Check if we need to parse arguments. + if i > j { + m.Params = strings.Split(raw[j:i-1], string(space)) + } + + m.Trailing = raw[i+1:] + + // We need to re-encode the trailing argument even if it was empty. + if len(m.Trailing) <= 0 { + m.EmptyTrailing = true + } + + return m + +} + +// Len calculates the length of the string representation of this message. +func (m *Message) Len() (length int) { + + if m.Prefix != nil { + length = m.Prefix.Len() + 2 // Include prefix and trailing space + } + + length = length + len(m.Command) + + if len(m.Params) > 0 { + length = length + len(m.Params) + for _, param := range m.Params { + length = length + len(param) + } + } + + if len(m.Trailing) > 0 || m.EmptyTrailing { + length = length + len(m.Trailing) + 2 // Include prefix and space + } + + return +} + +// Bytes returns a []byte representation of this message. +// +// As noted in rfc2812 section 2.3, messages should not exceed 512 characters +// in length. This method forces that limit by discarding any characters +// exceeding the length limit. +func (m *Message) Bytes() []byte { + + buffer := new(bytes.Buffer) + + // Message prefix + if m.Prefix != nil { + buffer.WriteByte(prefix) + m.Prefix.writeTo(buffer) + buffer.WriteByte(space) + } + + // Command is required + buffer.WriteString(m.Command) + + // Space separated list of arguments + if len(m.Params) > 0 { + buffer.WriteByte(space) + buffer.WriteString(strings.Join(m.Params, string(space))) + } + + if len(m.Trailing) > 0 || m.EmptyTrailing { + buffer.WriteByte(space) + buffer.WriteByte(prefix) + buffer.WriteString(m.Trailing) + } + + // We need the limit the buffer length. + if buffer.Len() > (maxLength) { + buffer.Truncate(maxLength) + } + + return buffer.Bytes() +} + +// String returns a string representation of this message. +// +// As noted in rfc2812 section 2.3, messages should not exceed 512 characters +// in length. This method forces that limit by discarding any characters +// exceeding the length limit. +func (m *Message) String() string { + return string(m.Bytes()) +} |