summaryrefslogtreecommitdiffstats
path: root/vendor/github.com/lrstanley/girc/format.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/lrstanley/girc/format.go')
-rw-r--r--vendor/github.com/lrstanley/girc/format.go350
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])
+}