rename Prefix to Source

This commit is contained in:
Liam Stanley 2016-11-19 09:36:17 -05:00
parent 3bd3b57d1e
commit a27267a822
5 changed files with 202 additions and 194 deletions

@ -3,7 +3,7 @@ go:
- 1.7.1
- tip
script:
- go test
- go test -v
- go tool vet -v -all .
branches:
only:

119
event.go

@ -10,112 +10,15 @@ import (
)
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 510 (2 for line endings)
space byte = 0x20 // separator
maxLength = 510 // maximum length is 510 (2 for line endings)
)
func cutsetFunc(r rune) bool {
// Characters to trim from prefixes/messages.
// cutCRFunc is used to trim CR characters from prefixes/messages.
func cutCRFunc(r rune) bool {
return r == '\r' || r == '\n'
}
// Prefix represents the sender of an IRC event, 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 := strings.IndexByte(raw, prefixUser)
host := strings.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 prefix
func (p *Prefix) Len() (length int) {
length = len(p.Name)
if len(p.User) > 0 {
length = 1 + length + len(p.User)
}
if len(p.Host) > 0 {
length = 1 + length + len(p.Host)
}
return
}
// Bytes returns a []byte representation of prefix
func (p *Prefix) Bytes() []byte {
buffer := new(bytes.Buffer)
p.writeTo(buffer)
return buffer.Bytes()
}
// String returns a string representation of prefix
func (p *Prefix) String() (s 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 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
}
// writeTo is an utility function to write the prefix to the bytes.Buffer in Event.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
}
// Event represents an IRC protocol message, see RFC1459 section 2.3.1
//
// <message> :: [':' <prefix> <SPACE>] <command> <params> <crlf>
@ -129,7 +32,7 @@ func (p *Prefix) writeTo(buffer *bytes.Buffer) {
// CR or LF>
// <crlf> :: CR LF
type Event struct {
*Prefix // The source of the event
*Source // The source of the event
Command string // the IRC command, e.g. JOIN, PRIVMSG, KILL
Params []string // parameters to the command. Commonly nickname, channel, etc
Trailing string // any trailing data. e.g. with a PRIVMSG, this is the message text
@ -141,7 +44,7 @@ type Event struct {
// Returns nil if the Event is invalid.
func ParseEvent(raw string) (e *Event) {
// ignore empty events
if raw = strings.TrimFunc(raw, cutsetFunc); len(raw) < 2 {
if raw = strings.TrimFunc(raw, cutCRFunc); len(raw) < 2 {
return nil
}
@ -157,7 +60,7 @@ func ParseEvent(raw string) (e *Event) {
return nil
}
e.Prefix = ParsePrefix(raw[1:i])
e.Source = ParseSource(raw[1:i])
i++ // skip space at the end of the prefix
}
@ -204,8 +107,8 @@ func ParseEvent(raw string) (e *Event) {
// Len calculates the length of the string representation of event
func (e *Event) Len() (length int) {
if e.Prefix != nil {
length = e.Prefix.Len() + 2 // include prefix and trailing space
if e.Source != nil {
length = e.Source.Len() + 2 // include prefix and trailing space
}
length = length + len(e.Command)
@ -234,9 +137,9 @@ func (e *Event) Bytes() []byte {
buffer := new(bytes.Buffer)
// event prefix
if e.Prefix != nil {
if e.Source != nil {
buffer.WriteByte(prefix)
e.Prefix.writeTo(buffer)
e.Source.writeTo(buffer)
buffer.WriteByte(space)
}

@ -72,7 +72,7 @@ func handleJOIN(c *Client, e Event) {
c.state.createChanIfNotExists(e.Params[0])
if e.Prefix.Name == c.GetNick() {
if e.Source.Name == c.GetNick() {
// If it's us, don't just add our user to the list. Run a WHO which
// will tell us who exactly is in the channel.
c.Who(e.Params[0])
@ -80,7 +80,7 @@ func handleJOIN(c *Client, e Event) {
}
// Create the user in state. Only WHO the user, which is more efficient.
c.Who(e.Prefix.Name)
c.Who(e.Source.Name)
}
// handlePART ensures that the state is clean of old user and channel entries.
@ -89,12 +89,12 @@ func handlePART(c *Client, e Event) {
return
}
if e.Prefix.Name == c.GetNick() {
if e.Source.Name == c.GetNick() {
c.state.deleteChannel(e.Params[0])
return
}
c.state.deleteUser(e.Prefix.Name)
c.state.deleteUser(e.Source.Name)
}
// handlWHO updates our internal tracking of users/channels with WHO/WHOX
@ -149,9 +149,9 @@ func handleNICK(c *Client, e Event) {
return
}
c.state.renameUser(e.Prefix.Name, e.Params[0])
c.state.renameUser(e.Source.Name, e.Params[0])
}
func handleQUIT(c *Client, e Event) {
c.state.deleteUser(e.Prefix.Name)
c.state.deleteUser(e.Source.Name)
}

79
main.go

@ -16,7 +16,6 @@
package girc
import (
"bytes"
"crypto/tls"
"errors"
"fmt"
@ -421,81 +420,3 @@ func (c *Client) SendRaw(raw string) {
func (c *Client) SendRawf(format string, a ...interface{}) {
c.SendRaw(fmt.Sprintf(format, a...))
}
// contains '*', even though this isn't RFC compliant, it's commonly used
var validChannelPrefixes = [...]string{"&", "#", "+", "!", "*"}
// IsValidChannel checks if channel is an RFC complaint channel or not
//
// channel = ( "#" / "+" / ( "!" channelid ) / "&" ) chanstring
// [ ":" chanstring ]
// chanstring = 0x01-0x07 / 0x08-0x09 / 0x0B-0x0C / 0x0E-0x1F / 0x21-0x2B
// chanstring = / 0x2D-0x39 / 0x3B-0xFF
// ; any octet except NUL, BELL, CR, LF, " ", "," and ":"
// channelid = 5( 0x41-0x5A / digit ) ; 5( A-Z / 0-9 )
func IsValidChannel(channel string) bool {
if len(channel) <= 1 || len(channel) > 50 {
return false
}
// #, +, !<channelid>, or &
// Including "*" in the prefix list, as this is commonly used (e.g. ZNC)
if bytes.IndexByte([]byte{0x21, 0x23, 0x26, 0x2A, 0x2B}, channel[0]) == -1 {
return false
}
// !<channelid> -- not very commonly supported, but we'll check it anyway.
// The ID must be 5 chars. This means min-channel size should be:
// 1 (prefix) + 5 (id) + 1 (+, channel name)
if channel[0] == 0x21 {
if len(channel) < 7 {
return false
}
// check for valid ID
for i := 1; i < 6; i++ {
if (channel[i] < 0x30 || channel[i] > 0x39) && (channel[i] < 0x41 || channel[i] > 0x5A) {
return false
}
}
}
// Check for invalid octets here.
bad := []byte{0x00, 0x07, 0x0D, 0x0A, 0x20, 0x2C, 0x3A}
for i := 1; i < len(channel); i++ {
if bytes.IndexByte(bad, channel[i]) != -1 {
return false
}
}
return true
}
// IsValidNick valids an IRC nickame. Note that this does not valid IRC
// nickname length.
//
// nickname = ( letter / special ) *8( letter / digit / special / "-" )
// letter = 0x41-0x5A / 0x61-0x7A
// digit = 0x30-0x39
// special = 0x5B-0x60 / 0x7B-0x7D
func IsValidNick(nick string) bool {
if len(nick) <= 0 {
return false
}
// Check the first index. Some characters aren't allowed for the first
// index of an IRC nickname.
if nick[0] < 0x41 || nick[0] > 0x7D {
// a-z, A-Z, and _\[]{}^|
return false
}
for i := 1; i < len(nick); i++ {
if (nick[i] < 0x41 || nick[i] > 0x7D) && (nick[i] < 0x30 || nick[i] > 0x39) && nick[i] != 0x2D {
// a-z, A-Z, 0-9, -, and _\[]{}^|
return false
}
}
return true
}

184
source.go Normal file

@ -0,0 +1,184 @@
// Copyright 2016 Liam Stanley <me@liamstanley.io>. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
package girc
import (
"bytes"
"strings"
)
const (
prefix byte = 0x3A // prefix or last argument
prefixUser byte = 0x21 // username
prefixHost byte = 0x40 // hostname
)
// Source represents the sender of an IRC event, see RFC1459 section 2.3.1
// <servername> | <nick> [ '!' <user> ] [ '@' <host> ]
type Source struct {
Name string // Nick or servername
User string // Username
Host string // Hostname
}
// ParseSource takes a string and attempts to create a Source struct.
func ParseSource(raw string) (src *Source) {
src = new(Source)
user := strings.IndexByte(raw, prefixUser)
host := strings.IndexByte(raw, prefixHost)
switch {
case user > 0 && host > user:
src.Name = raw[:user]
src.User = raw[user+1 : host]
src.Host = raw[host+1:]
case user > 0:
src.Name = raw[:user]
src.User = raw[user+1:]
case host > 0:
src.Name = raw[:host]
src.Host = raw[host+1:]
default:
src.Name = raw
}
return src
}
// Len calculates the length of the string representation of prefix
func (s *Source) Len() (length int) {
length = len(s.Name)
if len(s.User) > 0 {
length = 1 + length + len(s.User)
}
if len(s.Host) > 0 {
length = 1 + length + len(s.Host)
}
return
}
// Bytes returns a []byte representation of prefix
func (s *Source) Bytes() []byte {
buffer := new(bytes.Buffer)
s.writeTo(buffer)
return buffer.Bytes()
}
// String returns a string representation of prefix
func (s *Source) String() (out string) {
out = s.Name
if len(s.User) > 0 {
out = out + string(prefixUser) + s.User
}
if len(s.Host) > 0 {
out = out + string(prefixHost) + s.Host
}
return
}
// IsHostmask returns true if prefix looks like a user hostmask
func (s *Source) IsHostmask() bool {
return len(s.User) > 0 && len(s.Host) > 0
}
// IsServer returns true if this prefix looks like a server name.
func (s *Source) IsServer() bool {
return len(s.User) <= 0 && len(s.Host) <= 0
}
// writeTo is an utility function to write the prefix to the bytes.Buffer in Event.String()
func (s *Source) writeTo(buffer *bytes.Buffer) {
buffer.WriteString(s.Name)
if len(s.User) > 0 {
buffer.WriteByte(prefixUser)
buffer.WriteString(s.User)
}
if len(s.Host) > 0 {
buffer.WriteByte(prefixHost)
buffer.WriteString(s.Host)
}
return
}
// IsValidChannel checks if channel is an RFC complaint channel or not
//
// channel = ( "#" / "+" / ( "!" channelid ) / "&" ) chanstring
// [ ":" chanstring ]
// chanstring = 0x01-0x07 / 0x08-0x09 / 0x0B-0x0C / 0x0E-0x1F / 0x21-0x2B
// chanstring = / 0x2D-0x39 / 0x3B-0xFF
// ; any octet except NUL, BELL, CR, LF, " ", "," and ":"
// channelid = 5( 0x41-0x5A / digit ) ; 5( A-Z / 0-9 )
func IsValidChannel(channel string) bool {
if len(channel) <= 1 || len(channel) > 50 {
return false
}
// #, +, !<channelid>, or &
// Including "*" in the prefix list, as this is commonly used (e.g. ZNC)
if bytes.IndexByte([]byte{0x21, 0x23, 0x26, 0x2A, 0x2B}, channel[0]) == -1 {
return false
}
// !<channelid> -- not very commonly supported, but we'll check it anyway.
// The ID must be 5 chars. This means min-channel size should be:
// 1 (prefix) + 5 (id) + 1 (+, channel name)
if channel[0] == 0x21 {
if len(channel) < 7 {
return false
}
// check for valid ID
for i := 1; i < 6; i++ {
if (channel[i] < 0x30 || channel[i] > 0x39) && (channel[i] < 0x41 || channel[i] > 0x5A) {
return false
}
}
}
// Check for invalid octets here.
bad := []byte{0x00, 0x07, 0x0D, 0x0A, 0x20, 0x2C, 0x3A}
for i := 1; i < len(channel); i++ {
if bytes.IndexByte(bad, channel[i]) != -1 {
return false
}
}
return true
}
// IsValidNick valids an IRC nickame. Note that this does not valid IRC
// nickname length.
//
// nickname = ( letter / special ) *8( letter / digit / special / "-" )
// letter = 0x41-0x5A / 0x61-0x7A
// digit = 0x30-0x39
// special = 0x5B-0x60 / 0x7B-0x7D
func IsValidNick(nick string) bool {
if len(nick) <= 0 {
return false
}
// Check the first index. Some characters aren't allowed for the first
// index of an IRC nickname.
if nick[0] < 0x41 || nick[0] > 0x7D {
// a-z, A-Z, and _\[]{}^|
return false
}
for i := 1; i < len(nick); i++ {
if (nick[i] < 0x41 || nick[i] > 0x7D) && (nick[i] < 0x30 || nick[i] > 0x39) && nick[i] != 0x2D {
// a-z, A-Z, 0-9, -, and _\[]{}^|
return false
}
}
return true
}