diff options
Diffstat (limited to 'vendor/github.com/lrstanley/girc/format.go')
-rw-r--r-- | vendor/github.com/lrstanley/girc/format.go | 350 |
1 files changed, 350 insertions, 0 deletions
diff --git a/vendor/github.com/lrstanley/girc/format.go b/vendor/github.com/lrstanley/girc/format.go new file mode 100644 index 00000000..5d32f8ad --- /dev/null +++ b/vendor/github.com/lrstanley/girc/format.go @@ -0,0 +1,350 @@ +// Copyright (c) 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" + "fmt" + "regexp" + "strings" +) + +const ( + fmtOpenChar = 0x7B // { + fmtCloseChar = 0x7D // } +) + +var fmtColors = map[string]int{ + "white": 0, + "black": 1, + "blue": 2, + "navy": 2, + "green": 3, + "red": 4, + "brown": 5, + "maroon": 5, + "purple": 6, + "gold": 7, + "olive": 7, + "orange": 7, + "yellow": 8, + "lightgreen": 9, + "lime": 9, + "teal": 10, + "cyan": 11, + "lightblue": 12, + "royal": 12, + "fuchsia": 13, + "lightpurple": 13, + "pink": 13, + "gray": 14, + "grey": 14, + "lightgrey": 15, + "silver": 15, +} + +var fmtCodes = map[string]string{ + "bold": "\x02", + "b": "\x02", + "italic": "\x1d", + "i": "\x1d", + "reset": "\x0f", + "r": "\x0f", + "clear": "\x03", + "c": "\x03", // Clears formatting. + "reverse": "\x16", + "underline": "\x1f", + "ul": "\x1f", + "ctcp": "\x01", // CTCP/ACTION delimiter. +} + +// Fmt takes format strings like "{red}" or "{red,blue}" (for background +// colors) and turns them into the resulting ASCII format/color codes for IRC. +// See format.go for the list of supported format codes allowed. +// +// For example: +// +// client.Message("#channel", Fmt("{red}{b}Hello {red,blue}World{c}")) +func Fmt(text string) string { + var last = -1 + for i := 0; i < len(text); i++ { + if text[i] == fmtOpenChar { + last = i + continue + } + + if text[i] == fmtCloseChar && last > -1 { + code := strings.ToLower(text[last+1 : i]) + + // Check to see if they're passing in a second (background) color + // as {fgcolor,bgcolor}. + var secondary string + if com := strings.Index(code, ","); com > -1 { + secondary = code[com+1:] + code = code[:com] + } + + var repl string + + if color, ok := fmtColors[code]; ok { + repl = fmt.Sprintf("\x03%02d", color) + } + + if repl != "" && secondary != "" { + if color, ok := fmtColors[secondary]; ok { + repl += fmt.Sprintf(",%02d", color) + } + } + + if repl == "" { + if fmtCode, ok := fmtCodes[code]; ok { + repl = fmtCode + } + } + + next := len(text[:last]+repl) - 1 + text = text[:last] + repl + text[i+1:] + last = -1 + i = next + continue + } + + if last > -1 { + // A-Z, a-z, and "," + if text[i] != 0x2c && (text[i] <= 0x41 || text[i] >= 0x5a) && (text[i] <= 0x61 || text[i] >= 0x7a) { + last = -1 + continue + } + } + } + + return text +} + +// TrimFmt strips all "{fmt}" formatting strings from the input text. +// See Fmt() for more information. +func TrimFmt(text string) string { + for color := range fmtColors { + text = strings.Replace(text, "{"+color+"}", "", -1) + } + for code := range fmtCodes { + text = strings.Replace(text, "{"+code+"}", "", -1) + } + + return text +} + +// This is really the only fastest way of doing this (marginably better than +// actually trying to parse it manually.) +var reStripColor = regexp.MustCompile(`\x03([019]?[0-9](,[019]?[0-9])?)?`) + +// StripRaw tries to strip all ASCII format codes that are used for IRC. +// Primarily, foreground/background colors, and other control bytes like +// reset, bold, italic, reverse, etc. This also is done in a specific way +// in order to ensure no truncation of other non-irc formatting. +func StripRaw(text string) string { + text = reStripColor.ReplaceAllString(text, "") + + for _, code := range fmtCodes { + text = strings.Replace(text, code, "", -1) + } + + return text +} + +// IsValidChannel validates if channel is an RFC complaint channel or not. +// +// NOTE: If you are using this to validate a channel that contains a channel +// ID, (!<channelid>NAME), this only supports the standard 5 character length. +// +// NOTE: If you do not need to validate against servers that support unicode, +// you may want to ensure that all channel chars are within the range of +// all ASCII printable chars. This function will NOT do that for +// compatibility reasons. +// +// 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) + // On some networks, this may be extended with ISUPPORT capabilities, + // however this is extremely uncommon. + 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 validates an IRC nickame. Note that this does not validate +// 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 + } + + nick = ToRFC1459(nick) + + // 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 +} + +// IsValidUser validates an IRC ident/username. Note that this does not +// validate IRC ident length. +// +// The validation checks are much like what characters are allowed with an +// IRC nickname (see IsValidNick()), however an ident/username can: +// +// 1. Must either start with alphanumberic char, or "~" then alphanumberic +// char. +// +// 2. Contain a "." (period), for use with "first.last". Though, this may +// not be supported on all networks. Some limit this to only a single period. +// +// Per RFC: +// user = 1*( %x01-09 / %x0B-0C / %x0E-1F / %x21-3F / %x41-FF ) +// ; any octet except NUL, CR, LF, " " and "@" +func IsValidUser(name string) bool { + if len(name) <= 0 { + return false + } + + name = ToRFC1459(name) + + // "~" is prepended (commonly) if there was no ident server response. + if name[0] == 0x7E { + // Means name only contained "~". + if len(name) < 2 { + return false + } + + name = name[1:] + } + + // Check to see if the first index is alphanumeric. + if (name[0] < 0x41 || name[0] > 0x4A) && (name[0] < 0x61 || name[0] > 0x7A) && (name[0] < 0x30 || name[0] > 0x39) { + return false + } + + for i := 1; i < len(name); i++ { + if (name[i] < 0x41 || name[i] > 0x7D) && (name[i] < 0x30 || name[i] > 0x39) && name[i] != 0x2D && name[i] != 0x2E { + // a-z, A-Z, 0-9, -, and _\[]{}^| + return false + } + } + + return true +} + +// ToRFC1459 converts a string to the stripped down conversion within RFC +// 1459. This will do things like replace an "A" with an "a", "[]" with "{}", +// and so forth. Useful to compare two nicknames or channels. +func ToRFC1459(input string) (out string) { + for i := 0; i < len(input); i++ { + if input[i] >= 65 && input[i] <= 94 { + out += string(rune(input[i]) + 32) + } else { + out += string(input[i]) + } + } + + return out +} + +const globChar = "*" + +// Glob will test a string pattern, potentially containing globs, against a +// string. The glob character is *. +func Glob(input, match string) bool { + // Empty pattern. + if match == "" { + return input == match + } + + // If a glob, match all. + if match == globChar { + return true + } + + parts := strings.Split(match, globChar) + + if len(parts) == 1 { + // No globs, test for equality. + return input == match + } + + leadingGlob, trailingGlob := strings.HasPrefix(match, globChar), strings.HasSuffix(match, globChar) + last := len(parts) - 1 + + // Check prefix first. + if !leadingGlob && !strings.HasPrefix(input, parts[0]) { + return false + } + + // Check middle section. + for i := 1; i < last; i++ { + if !strings.Contains(input, parts[i]) { + return false + } + + // Trim already-evaluated text from input during loop over match + // text. + idx := strings.Index(input, parts[i]) + len(parts[i]) + input = input[idx:] + } + + // Check suffix last. + return trailingGlob || strings.HasSuffix(input, parts[last]) +} |