diff options
Diffstat (limited to 'vendor/github.com/sorcix')
-rw-r--r-- | vendor/github.com/sorcix/irc/LICENSE | 22 | ||||
-rw-r--r-- | vendor/github.com/sorcix/irc/constants.go | 298 | ||||
-rw-r--r-- | vendor/github.com/sorcix/irc/ctcp/ctcp.go | 144 | ||||
-rw-r--r-- | vendor/github.com/sorcix/irc/ctcp/doc.go | 31 | ||||
-rw-r--r-- | vendor/github.com/sorcix/irc/doc.go | 36 | ||||
-rw-r--r-- | vendor/github.com/sorcix/irc/message.go | 308 | ||||
-rw-r--r-- | vendor/github.com/sorcix/irc/stream.go | 134 | ||||
-rw-r--r-- | vendor/github.com/sorcix/irc/strings.go | 17 | ||||
-rw-r--r-- | vendor/github.com/sorcix/irc/strings_legacy.go | 22 |
9 files changed, 1012 insertions, 0 deletions
diff --git a/vendor/github.com/sorcix/irc/LICENSE b/vendor/github.com/sorcix/irc/LICENSE new file mode 100644 index 00000000..10cecc4c --- /dev/null +++ b/vendor/github.com/sorcix/irc/LICENSE @@ -0,0 +1,22 @@ +Copyright 2014 Vic Demuzere + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/github.com/sorcix/irc/constants.go b/vendor/github.com/sorcix/irc/constants.go new file mode 100644 index 00000000..d4812ba3 --- /dev/null +++ b/vendor/github.com/sorcix/irc/constants.go @@ -0,0 +1,298 @@ +// Copyright 2014 Vic Demuzere +// +// Use of this source code is governed by the MIT license. + +package irc + +// Various prefixes extracted from RFC1459. +const ( + Channel = '#' // Normal channel + Distributed = '&' // Distributed channel + + Owner = '~' // Channel owner +q (non-standard) + Admin = '&' // Channel admin +a (non-standard) + Operator = '@' // Channel operator +o + HalfOperator = '%' // Channel half operator +h (non-standard) + Voice = '+' // User has voice +v +) + +// User modes as defined by RFC1459 section 4.2.3.2. +const ( + UserModeInvisible = 'i' // User is invisible + UserModeServerNotices = 's' // User wants to receive server notices + UserModeWallops = 'w' // User wants to receive Wallops + UserModeOperator = 'o' // Server operator +) + +// Channel modes as defined by RFC1459 section 4.2.3.1 +const ( + ModeOperator = 'o' // Operator privileges + ModeVoice = 'v' // Ability to speak on a moderated channel + ModePrivate = 'p' // Private channel + ModeSecret = 's' // Secret channel + ModeInviteOnly = 'i' // Users can't join without invite + ModeTopic = 't' // Topic can only be set by an operator + ModeModerated = 'm' // Only voiced users and operators can talk + ModeLimit = 'l' // User limit + ModeKey = 'k' // Channel password + + ModeOwner = 'q' // Owner privileges (non-standard) + ModeAdmin = 'a' // Admin privileges (non-standard) + ModeHalfOperator = 'h' // Half-operator privileges (non-standard) +) + +// IRC commands extracted from RFC2812 section 3 and RFC2813 section 4. +const ( + PASS = "PASS" + NICK = "NICK" + USER = "USER" + OPER = "OPER" + MODE = "MODE" + SERVICE = "SERVICE" + QUIT = "QUIT" + SQUIT = "SQUIT" + JOIN = "JOIN" + PART = "PART" + TOPIC = "TOPIC" + NAMES = "NAMES" + LIST = "LIST" + INVITE = "INVITE" + KICK = "KICK" + PRIVMSG = "PRIVMSG" + NOTICE = "NOTICE" + MOTD = "MOTD" + LUSERS = "LUSERS" + VERSION = "VERSION" + STATS = "STATS" + LINKS = "LINKS" + TIME = "TIME" + CONNECT = "CONNECT" + TRACE = "TRACE" + ADMIN = "ADMIN" + INFO = "INFO" + SERVLIST = "SERVLIST" + SQUERY = "SQUERY" + WHO = "WHO" + WHOIS = "WHOIS" + WHOWAS = "WHOWAS" + KILL = "KILL" + PING = "PING" + PONG = "PONG" + ERROR = "ERROR" + AWAY = "AWAY" + REHASH = "REHASH" + DIE = "DIE" + RESTART = "RESTART" + SUMMON = "SUMMON" + USERS = "USERS" + WALLOPS = "WALLOPS" + USERHOST = "USERHOST" + ISON = "ISON" + SERVER = "SERVER" + NJOIN = "NJOIN" +) + +// Numeric IRC replies extracted from RFC2812 section 5. +const ( + RPL_WELCOME = "001" + RPL_YOURHOST = "002" + RPL_CREATED = "003" + RPL_MYINFO = "004" + RPL_BOUNCE = "005" + RPL_ISUPPORT = "005" + RPL_USERHOST = "302" + RPL_ISON = "303" + RPL_AWAY = "301" + RPL_UNAWAY = "305" + RPL_NOWAWAY = "306" + RPL_WHOISUSER = "311" + RPL_WHOISSERVER = "312" + RPL_WHOISOPERATOR = "313" + RPL_WHOISIDLE = "317" + RPL_ENDOFWHOIS = "318" + RPL_WHOISCHANNELS = "319" + RPL_WHOWASUSER = "314" + RPL_ENDOFWHOWAS = "369" + RPL_LISTSTART = "321" + RPL_LIST = "322" + RPL_LISTEND = "323" + RPL_UNIQOPIS = "325" + RPL_CHANNELMODEIS = "324" + RPL_NOTOPIC = "331" + RPL_TOPIC = "332" + RPL_INVITING = "341" + RPL_SUMMONING = "342" + RPL_INVITELIST = "346" + RPL_ENDOFINVITELIST = "347" + RPL_EXCEPTLIST = "348" + RPL_ENDOFEXCEPTLIST = "349" + RPL_VERSION = "351" + RPL_WHOREPLY = "352" + RPL_ENDOFWHO = "315" + RPL_NAMREPLY = "353" + RPL_ENDOFNAMES = "366" + RPL_LINKS = "364" + RPL_ENDOFLINKS = "365" + RPL_BANLIST = "367" + RPL_ENDOFBANLIST = "368" + RPL_INFO = "371" + RPL_ENDOFINFO = "374" + RPL_MOTDSTART = "375" + RPL_MOTD = "372" + RPL_ENDOFMOTD = "376" + RPL_YOUREOPER = "381" + RPL_REHASHING = "382" + RPL_YOURESERVICE = "383" + RPL_TIME = "391" + RPL_USERSSTART = "392" + RPL_USERS = "393" + RPL_ENDOFUSERS = "394" + RPL_NOUSERS = "395" + RPL_TRACELINK = "200" + RPL_TRACECONNECTING = "201" + RPL_TRACEHANDSHAKE = "202" + RPL_TRACEUNKNOWN = "203" + RPL_TRACEOPERATOR = "204" + RPL_TRACEUSER = "205" + RPL_TRACESERVER = "206" + RPL_TRACESERVICE = "207" + RPL_TRACENEWTYPE = "208" + RPL_TRACECLASS = "209" + RPL_TRACERECONNECT = "210" + RPL_TRACELOG = "261" + RPL_TRACEEND = "262" + RPL_STATSLINKINFO = "211" + RPL_STATSCOMMANDS = "212" + RPL_ENDOFSTATS = "219" + RPL_STATSUPTIME = "242" + RPL_STATSOLINE = "243" + RPL_UMODEIS = "221" + RPL_SERVLIST = "234" + RPL_SERVLISTEND = "235" + RPL_LUSERCLIENT = "251" + RPL_LUSEROP = "252" + RPL_LUSERUNKNOWN = "253" + RPL_LUSERCHANNELS = "254" + RPL_LUSERME = "255" + RPL_ADMINME = "256" + RPL_ADMINLOC1 = "257" + RPL_ADMINLOC2 = "258" + RPL_ADMINEMAIL = "259" + RPL_TRYAGAIN = "263" + ERR_NOSUCHNICK = "401" + ERR_NOSUCHSERVER = "402" + ERR_NOSUCHCHANNEL = "403" + ERR_CANNOTSENDTOCHAN = "404" + ERR_TOOMANYCHANNELS = "405" + ERR_WASNOSUCHNICK = "406" + ERR_TOOMANYTARGETS = "407" + ERR_NOSUCHSERVICE = "408" + ERR_NOORIGIN = "409" + ERR_NORECIPIENT = "411" + ERR_NOTEXTTOSEND = "412" + ERR_NOTOPLEVEL = "413" + ERR_WILDTOPLEVEL = "414" + ERR_BADMASK = "415" + ERR_UNKNOWNCOMMAND = "421" + ERR_NOMOTD = "422" + ERR_NOADMININFO = "423" + ERR_FILEERROR = "424" + ERR_NONICKNAMEGIVEN = "431" + ERR_ERRONEUSNICKNAME = "432" + ERR_NICKNAMEINUSE = "433" + ERR_NICKCOLLISION = "436" + ERR_UNAVAILRESOURCE = "437" + ERR_USERNOTINCHANNEL = "441" + ERR_NOTONCHANNEL = "442" + ERR_USERONCHANNEL = "443" + ERR_NOLOGIN = "444" + ERR_SUMMONDISABLED = "445" + ERR_USERSDISABLED = "446" + ERR_NOTREGISTERED = "451" + ERR_NEEDMOREPARAMS = "461" + ERR_ALREADYREGISTRED = "462" + ERR_NOPERMFORHOST = "463" + ERR_PASSWDMISMATCH = "464" + ERR_YOUREBANNEDCREEP = "465" + ERR_YOUWILLBEBANNED = "466" + ERR_KEYSET = "467" + ERR_CHANNELISFULL = "471" + ERR_UNKNOWNMODE = "472" + ERR_INVITEONLYCHAN = "473" + ERR_BANNEDFROMCHAN = "474" + ERR_BADCHANNELKEY = "475" + ERR_BADCHANMASK = "476" + ERR_NOCHANMODES = "477" + ERR_BANLISTFULL = "478" + ERR_NOPRIVILEGES = "481" + ERR_CHANOPRIVSNEEDED = "482" + ERR_CANTKILLSERVER = "483" + ERR_RESTRICTED = "484" + ERR_UNIQOPPRIVSNEEDED = "485" + ERR_NOOPERHOST = "491" + ERR_UMODEUNKNOWNFLAG = "501" + ERR_USERSDONTMATCH = "502" +) + +// IRC commands extracted from the IRCv3 spec at http://www.ircv3.org/. +const ( + CAP = "CAP" + CAP_LS = "LS" // Subcommand (param) + CAP_LIST = "LIST" // Subcommand (param) + CAP_REQ = "REQ" // Subcommand (param) + CAP_ACK = "ACK" // Subcommand (param) + CAP_NAK = "NAK" // Subcommand (param) + CAP_CLEAR = "CLEAR" // Subcommand (param) + CAP_END = "END" // Subcommand (param) + + AUTHENTICATE = "AUTHENTICATE" +) + +// Numeric IRC replies extracted from the IRCv3 spec. +const ( + RPL_LOGGEDIN = "900" + RPL_LOGGEDOUT = "901" + RPL_NICKLOCKED = "902" + RPL_SASLSUCCESS = "903" + ERR_SASLFAIL = "904" + ERR_SASLTOOLONG = "905" + ERR_SASLABORTED = "906" + ERR_SASLALREADY = "907" + RPL_SASLMECHS = "908" +) + +// RFC2812, section 5.3 +const ( + RPL_STATSCLINE = "213" + RPL_STATSNLINE = "214" + RPL_STATSILINE = "215" + RPL_STATSKLINE = "216" + RPL_STATSQLINE = "217" + RPL_STATSYLINE = "218" + RPL_SERVICEINFO = "231" + RPL_ENDOFSERVICES = "232" + RPL_SERVICE = "233" + RPL_STATSVLINE = "240" + RPL_STATSLLINE = "241" + RPL_STATSHLINE = "244" + RPL_STATSSLINE = "245" + RPL_STATSPING = "246" + RPL_STATSBLINE = "247" + RPL_STATSDLINE = "250" + RPL_NONE = "300" + RPL_WHOISCHANOP = "316" + RPL_KILLDONE = "361" + RPL_CLOSING = "362" + RPL_CLOSEEND = "363" + RPL_INFOSTART = "373" + RPL_MYPORTIS = "384" + ERR_NOSERVICEHOST = "492" +) + +// Other constants +const ( + ERR_TOOMANYMATCHES = "416" // Used on IRCNet + RPL_TOPICWHOTIME = "333" // From ircu, in use on Freenode + RPL_LOCALUSERS = "265" // From aircd, Hybrid, Hybrid, Bahamut, in use on Freenode + RPL_GLOBALUSERS = "266" // From aircd, Hybrid, Hybrid, Bahamut, in use on Freenode +) diff --git a/vendor/github.com/sorcix/irc/ctcp/ctcp.go b/vendor/github.com/sorcix/irc/ctcp/ctcp.go new file mode 100644 index 00000000..7ead788d --- /dev/null +++ b/vendor/github.com/sorcix/irc/ctcp/ctcp.go @@ -0,0 +1,144 @@ +// Copyright 2014 Vic Demuzere +// +// Use of this source code is governed by the MIT license. + +package ctcp + +// Sources: +// http://www.irchelp.org/irchelp/rfc/ctcpspec.html +// http://www.kvirc.net/doc/doc_ctcp_handling.html + +import ( + "fmt" + "runtime" + "strings" + "time" +) + +// Various constants used for formatting CTCP messages. +const ( + delimiter byte = 0x01 // Prefix and suffix for CTCP tagged messages. + space byte = 0x20 // Token separator + + empty = "" // The empty string + + timeFormat = time.RFC1123Z + versionFormat = "Go v%s (" + runtime.GOOS + ", " + runtime.GOARCH + ")" +) + +// Tags extracted from the CTCP spec. +const ( + ACTION = "ACTION" + PING = "PING" + PONG = "PONG" + VERSION = "VERSION" + USERINFO = "USERINFO" + CLIENTINFO = "CLIENTINFO" + FINGER = "FINGER" + SOURCE = "SOURCE" + TIME = "TIME" +) + +// Decode attempts to decode CTCP tagged data inside given message text. +// +// If the message text does not contain tagged data, ok will be false. +// +// <text> ::= <delim> <tag> [<SPACE> <message>] <delim> +// <delim> ::= 0x01 +// +func Decode(text string) (tag, message string, ok bool) { + + // Fast path, return if this text does not contain a CTCP message. + if len(text) < 3 || text[0] != delimiter || text[len(text)-1] != delimiter { + return empty, empty, false + } + + s := strings.IndexByte(text, space) + + if s < 0 { + + // Messages may contain only a tag. + return text[1 : len(text)-1], empty, true + } + + return text[1:s], text[s+1 : len(text)-1], true +} + +// Encode returns the IRC message text for CTCP tagged data. +// +// <text> ::= <delim> <tag> [<SPACE> <message>] <delim> +// <delim> ::= 0x01 +// +func Encode(tag, message string) (text string) { + + switch { + + // We can't build a valid CTCP tagged message without at least a tag. + case len(tag) <= 0: + return empty + + // Tagged data with a message + case len(message) > 0: + return string(delimiter) + tag + string(space) + message + string(delimiter) + + // Tagged data without a message + default: + return string(delimiter) + tag + string(delimiter) + + } +} + +// Action is a shortcut for Encode(ctcp.ACTION, message). +func Action(message string) string { + return Encode(ACTION, message) +} + +// Ping is a shortcut for Encode(ctcp.PING, message). +func Ping(message string) string { + return Encode(PING, message) +} + +// Pong is a shortcut for Encode(ctcp.PONG, message). +func Pong(message string) string { + return Encode(PONG, message) +} + +// Version is a shortcut for Encode(ctcp.VERSION, message). +func Version(message string) string { + return Encode(VERSION, message) +} + +// VersionReply is a shortcut for ENCODE(ctcp.VERSION, go version info). +func VersionReply() string { + return Encode(VERSION, fmt.Sprintf(versionFormat, runtime.Version())) +} + +// UserInfo is a shortcut for Encode(ctcp.USERINFO, message). +func UserInfo(message string) string { + return Encode(USERINFO, message) +} + +// ClientInfo is a shortcut for Encode(ctcp.CLIENTINFO, message). +func ClientInfo(message string) string { + return Encode(CLIENTINFO, message) +} + +// Finger is a shortcut for Encode(ctcp.FINGER, message). +func Finger(message string) string { + return Encode(FINGER, message) +} + +// Source is a shortcut for Encode(ctcp.SOURCE, message). +func Source(message string) string { + return Encode(SOURCE, message) +} + +// Time is a shortcut for Encode(ctcp.TIME, message). +func Time(message string) string { + return Encode(TIME, message) +} + +// TimeReply is a shortcut for Encode(ctcp.TIME, currenttime). +func TimeReply() string { + return Encode(TIME, time.Now().Format(timeFormat)) +} diff --git a/vendor/github.com/sorcix/irc/ctcp/doc.go b/vendor/github.com/sorcix/irc/ctcp/doc.go new file mode 100644 index 00000000..f0308d86 --- /dev/null +++ b/vendor/github.com/sorcix/irc/ctcp/doc.go @@ -0,0 +1,31 @@ +// Copyright 2014 Vic Demuzere +// +// Use of this source code is governed by the MIT license. + +// Package ctcp implements partial support for the Client-to-Client Protocol. +// +// CTCP defines extended messages using the standard PRIVMSG and NOTICE +// commands in IRC. This means that any CTCP messages are embedded inside the +// normal message text. Clients that don't support CTCP simply show +// the encoded message to the user. +// +// Most IRC clients support only a subset of the protocol, and only a few +// commands are actually used. This package aims to implement the most basic +// CTCP messages: a single command per IRC message. Quoting is not supported. +// +// Example using the irc.Message type: +// +// m := irc.ParseMessage(...) +// +// if tag, text, ok := ctcp.Decode(m.Trailing); ok { +// // This is a CTCP message. +// } else { +// // This is not a CTCP message. +// } +// +// Similar, for encoding messages: +// +// m.Trailing = ctcp.Encode("ACTION","wants a cookie!") +// +// Do not send a complete IRC message to Decode, it won't work. +package ctcp diff --git a/vendor/github.com/sorcix/irc/doc.go b/vendor/github.com/sorcix/irc/doc.go new file mode 100644 index 00000000..0effeb8a --- /dev/null +++ b/vendor/github.com/sorcix/irc/doc.go @@ -0,0 +1,36 @@ +// Copyright 2014 Vic Demuzere +// +// Use of this source code is governed by the MIT license. + +// Package irc allows your application to speak the IRC protocol. +// +// The Message and Prefix structs provide translation to and from raw IRC messages: +// +// // Parse the IRC-encoded data and store the result in a new struct: +// message := irc.ParseMessage(raw) +// +// // Translate back to a raw IRC message string: +// raw = message.String() +// +// Decoder and Encoder can be used to decode and encode messages in a stream: +// +// // Create a decoder that reads from given io.Reader +// dec := irc.NewDecoder(reader) +// +// // Decode the next IRC message +// message, err := dec.Decode() +// +// // Create an encoder that writes to given io.Writer +// enc := irc.NewEncoder(writer) +// +// // Send a message to the writer. +// enc.Encode(message) +// +// The Conn type combines an Encoder and Decoder for a duplex connection. +// +// c, err := irc.Dial("irc.server.net:6667") +// +// // Methods from both Encoder and Decoder are available +// message, err := c.Decode() +// +package irc 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()) +} diff --git a/vendor/github.com/sorcix/irc/stream.go b/vendor/github.com/sorcix/irc/stream.go new file mode 100644 index 00000000..c4af9af1 --- /dev/null +++ b/vendor/github.com/sorcix/irc/stream.go @@ -0,0 +1,134 @@ +// Copyright 2014 Vic Demuzere +// +// Use of this source code is governed by the MIT license. + +package irc + +import ( + "bufio" + "io" + "net" + "sync" +) + +// Messages are delimited with CR and LF line endings, +// we're using the last one to split the stream. Both are removed +// during message parsing. +const delim byte = '\n' + +var endline = []byte("\r\n") + +// A Conn represents an IRC network protocol connection. +// It consists of an Encoder and Decoder to manage I/O. +type Conn struct { + Encoder + Decoder + + conn io.ReadWriteCloser +} + +// NewConn returns a new Conn using rwc for I/O. +func NewConn(rwc io.ReadWriteCloser) *Conn { + return &Conn{ + Encoder: Encoder{ + writer: rwc, + }, + Decoder: Decoder{ + reader: bufio.NewReader(rwc), + }, + conn: rwc, + } +} + +// Dial connects to the given address using net.Dial and +// then returns a new Conn for the connection. +func Dial(addr string) (*Conn, error) { + c, err := net.Dial("tcp", addr) + + if err != nil { + return nil, err + } + + return NewConn(c), nil +} + +// Close closes the underlying ReadWriteCloser. +func (c *Conn) Close() error { + return c.conn.Close() +} + +// A Decoder reads Message objects from an input stream. +type Decoder struct { + reader *bufio.Reader + line string + mu sync.Mutex +} + +// NewDecoder returns a new Decoder that reads from r. +func NewDecoder(r io.Reader) *Decoder { + return &Decoder{ + reader: bufio.NewReader(r), + } +} + +// Decode attempts to read a single Message from the stream. +// +// Returns a non-nil error if the read failed. +func (dec *Decoder) Decode() (m *Message, err error) { + + dec.mu.Lock() + dec.line, err = dec.reader.ReadString(delim) + dec.mu.Unlock() + + if err != nil { + return nil, err + } + + return ParseMessage(dec.line), nil +} + +// An Encoder writes Message objects to an output stream. +type Encoder struct { + writer io.Writer + mu sync.Mutex +} + +// NewEncoder returns a new Encoder that writes to w. +func NewEncoder(w io.Writer) *Encoder { + return &Encoder{ + writer: w, + } +} + +// Encode writes the IRC encoding of m to the stream. +// +// This method may be used from multiple goroutines. +// +// Returns an non-nil error if the write to the underlying stream stopped early. +func (enc *Encoder) Encode(m *Message) (err error) { + + _, err = enc.Write(m.Bytes()) + + return +} + +// Write writes len(p) bytes from p followed by CR+LF. +// +// This method can be used simultaneously from multiple goroutines, +// it guarantees to serialize access. However, writing a single IRC message +// using multiple Write calls will cause corruption. +func (enc *Encoder) Write(p []byte) (n int, err error) { + + enc.mu.Lock() + n, err = enc.writer.Write(p) + + if err != nil { + enc.mu.Unlock() + return + } + + _, err = enc.writer.Write(endline) + enc.mu.Unlock() + + return +} diff --git a/vendor/github.com/sorcix/irc/strings.go b/vendor/github.com/sorcix/irc/strings.go new file mode 100644 index 00000000..550739f4 --- /dev/null +++ b/vendor/github.com/sorcix/irc/strings.go @@ -0,0 +1,17 @@ +// Copyright 2014 Vic Demuzere +// +// Use of this source code is governed by the MIT license. + +// +build go1.2 + +// Documented in strings_legacy.go + +package irc + +import ( + "strings" +) + +func indexByte(s string, c byte) int { + return strings.IndexByte(s, c) +} diff --git a/vendor/github.com/sorcix/irc/strings_legacy.go b/vendor/github.com/sorcix/irc/strings_legacy.go new file mode 100644 index 00000000..f9328ec7 --- /dev/null +++ b/vendor/github.com/sorcix/irc/strings_legacy.go @@ -0,0 +1,22 @@ +// Copyright 2014 Vic Demuzere +// +// Use of this source code is governed by the MIT license. + +// +build !go1.2 + +// Debian Wheezy only ships Go 1.0: +// https://github.com/sorcix/irc/issues/4 +// +// This code may be removed when Wheezy is no longer supported. + +package irc + +// indexByte implements strings.IndexByte for Go versions < 1.2. +func indexByte(s string, c byte) int { + for i := range s { + if s[i] == c { + return i + } + } + return -1 +} |