mirror of
https://github.com/IllusionMan1212/gorc.git
synced 2026-10-24 02:12:33 +00:00
feat: implement ISUPPORT and add support for a few easy features
fix: CAP ACK commands with no acknowledged capabilities causing an OOB. Also log unsupported and unknown capabilities and features.
This commit is contained in:
@@ -95,9 +95,13 @@ type Client struct {
|
|||||||
|
|
||||||
// The acknowledged capabilities
|
// The acknowledged capabilities
|
||||||
EnabledCapabilities Capabilities
|
EnabledCapabilities Capabilities
|
||||||
|
|
||||||
|
// The features currently enabled for this client
|
||||||
|
EnabledFeatures Features
|
||||||
}
|
}
|
||||||
|
|
||||||
type Capabilities map[string]string
|
type Capabilities map[string]string
|
||||||
|
type Features map[string]string
|
||||||
|
|
||||||
type MsgFmtOpts struct {
|
type MsgFmtOpts struct {
|
||||||
WithTimestamp bool
|
WithTimestamp bool
|
||||||
|
|||||||
+57
-54
@@ -58,7 +58,7 @@ const (
|
|||||||
NOTICE = "NOTICE" // Send notices between users and to channels.
|
NOTICE = "NOTICE" // Send notices between users and to channels.
|
||||||
|
|
||||||
// Operator Messages
|
// Operator Messages
|
||||||
KILL = "KILL" // CLose connection between a client and the server. IRC Operators only (do we need this ??)
|
KILL = "KILL" // Close connection between a client and the server. IRC Operators only (do we need this ??)
|
||||||
|
|
||||||
// Optional Messages
|
// Optional Messages
|
||||||
AWAY = "AWAY" // Indicate that the client (user) is away/afk/etc..
|
AWAY = "AWAY" // Indicate that the client (user) is away/afk/etc..
|
||||||
@@ -305,64 +305,67 @@ const (
|
|||||||
|
|
||||||
/* --- RPL_ISUPPORT Parameters --- */
|
/* --- RPL_ISUPPORT Parameters --- */
|
||||||
var Features = map[string]bool{
|
var Features = map[string]bool{
|
||||||
"ACCEPT": true,
|
"ACCEPT": false,
|
||||||
|
// The AWAYLEN parameter indicates the maximum length for the <reason> of an AWAY command.
|
||||||
|
// If an AWAY <reason> has more characters than this parameter, it may be silently truncated by the server before being passed on to other clients.
|
||||||
|
// Clients MAY receive an AWAY <reason> that has more characters than this parameter
|
||||||
"AWAYLEN": true,
|
"AWAYLEN": true,
|
||||||
"BOT": true,
|
"BOT": false,
|
||||||
"CALLERID": true,
|
"CALLERID": false,
|
||||||
"CASEMAPPING": true,
|
"CASEMAPPING": false,
|
||||||
"CHANLIMIT": true,
|
"CHANLIMIT": false,
|
||||||
"CHANMODES": true,
|
"CHANMODES": false,
|
||||||
"CHANNELLEN": true,
|
"CHANNELLEN": false,
|
||||||
"CHANTYPES": true, // Channel Types. Default is #. Available are #&
|
"CHANTYPES": false, // Channel Types. Default is #. Available are #&
|
||||||
"CHARSET": true, // Deprecated but might still be used
|
"CHARSET": false, // Deprecated but might still be used
|
||||||
"CLIENTVER": true, // Deprecated but might still be used
|
"CLIENTVER": false, // Deprecated but might still be used
|
||||||
"CNOTICE": true,
|
"CNOTICE": false,
|
||||||
"CPRIVMSG": true,
|
"CPRIVMSG": false,
|
||||||
"DEAF": true,
|
"DEAF": false,
|
||||||
"ELIST": true,
|
"ELIST": false,
|
||||||
"ESILENCE": true,
|
"ESILENCE": false,
|
||||||
"ETRACE": true,
|
"ETRACE": false,
|
||||||
"EXCEPTS": true,
|
"EXCEPTS": false,
|
||||||
"EXTBAN": true,
|
"EXTBAN": false,
|
||||||
"FNC": true, // Deprecated but might still be used
|
"FNC": false, // Deprecated but might still be used
|
||||||
"INVEX": true,
|
"INVEX": false,
|
||||||
"KEYLEN": true,
|
"KEYLEN": false,
|
||||||
"KICKLEN": true,
|
"KICKLEN": true,
|
||||||
"KNOCK": true,
|
"KNOCK": false,
|
||||||
"LINELEN": true, // Proposed
|
"LINELEN": false, // Proposed
|
||||||
"MAP": true, // Deprecated but might still be used
|
"MAP": false, // Deprecated but might still be used
|
||||||
"MAXBANS": true, // Deprecated but might still be used
|
"MAXBANS": false, // Deprecated but might still be used
|
||||||
"MAXCHANNELS": true, // Deprecated but might still be used
|
"MAXCHANNELS": false, // Deprecated but might still be used
|
||||||
"MAXLIST": true,
|
"MAXLIST": false,
|
||||||
"MAXNICKLEN": true,
|
"MAXNICKLEN": false,
|
||||||
"MAXPARA": true, // Deprecated but might still be used
|
"MAXPARA": false, // Deprecated but might still be used
|
||||||
"MAXTARGETS": true,
|
"MAXTARGETS": false,
|
||||||
"METADATA": true,
|
"METADATA": false,
|
||||||
"MODES": true,
|
"MODES": false,
|
||||||
"MONITOR": true,
|
"MONITOR": false,
|
||||||
"NAMESX": true, // Deprecated but might still be used
|
"NAMESX": false, // Deprecated but might still be used
|
||||||
"NETWORK": true,
|
"NETWORK": true,
|
||||||
"NICKLEN": true,
|
"NICKLEN": true,
|
||||||
"OVERRIDE": true,
|
"OVERRIDE": false,
|
||||||
"PREFIX": true,
|
"PREFIX": false,
|
||||||
"SAFELIST": true,
|
"SAFELIST": false,
|
||||||
"SECURELIST": true,
|
"SECURELIST": false,
|
||||||
"SILENCE": true,
|
"SILENCE": false,
|
||||||
"SSL": true, // Deprecated but might still be used
|
"SSL": false, // Deprecated but might still be used
|
||||||
"STARTTLS": true, // Deprecated but might still be used
|
"STARTTLS": false, // Deprecated but might still be used
|
||||||
"STATUSMSG": true,
|
"STATUSMSG": false,
|
||||||
"STD": true, // Deprecated but might still be used
|
"STD": false, // Deprecated but might still be used
|
||||||
"TARGMAX": true,
|
"TARGMAX": false,
|
||||||
"TOPICLEN": true,
|
"TOPICLEN": true,
|
||||||
"UHNAMES": true, // Deprecated but might still be used
|
"UHNAMES": false, // Deprecated but might still be used
|
||||||
"USERIP": true,
|
"USERIP": false,
|
||||||
"USERLEN": true, // Proposed
|
"USERLEN": false, // Proposed
|
||||||
"VBANLIST": true, // Deprecared but might still be used
|
"VBANLIST": false, // Deprecared but might still be used
|
||||||
"VLIST": true,
|
"VLIST": false,
|
||||||
"WALLCHOPS": true, // Deprecated but might still be used
|
"WALLCHOPS": false, // Deprecated but might still be used
|
||||||
"WALLVOICES": true, // Deprecated but might still be used
|
"WALLVOICES": false, // Deprecated but might still be used
|
||||||
"WATCH": true,
|
"WATCH": false,
|
||||||
"WHOX": true,
|
"WHOX": false,
|
||||||
}
|
}
|
||||||
|
|
||||||
var Capabilities = map[string]bool{
|
var Capabilities = map[string]bool{
|
||||||
|
|||||||
+49
-11
@@ -35,6 +35,7 @@ import (
|
|||||||
|
|
||||||
func ReadLoop(client *irc.Client) {
|
func ReadLoop(client *irc.Client) {
|
||||||
// 512 bytes as a base + 8192 additional bytes for tags
|
// 512 bytes as a base + 8192 additional bytes for tags
|
||||||
|
// as specified here: https://modern.ircdocs.horse/#message-format
|
||||||
r := bufio.NewReaderSize(client.TCPConn, 8192+512)
|
r := bufio.NewReaderSize(client.TCPConn, 8192+512)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
@@ -235,18 +236,22 @@ func handleCAP(msg irc.Message, client *irc.Client) {
|
|||||||
value = parts[1]
|
value = parts[1]
|
||||||
}
|
}
|
||||||
|
|
||||||
if commands.Capabilities[key] {
|
if capEnabled, ok := commands.Capabilities[key]; ok {
|
||||||
// When the final parameter approaches 510 bytes,
|
if capEnabled {
|
||||||
// we send multiple REQ commands
|
// When the final parameter approaches 510 bytes,
|
||||||
// (https://ircv3.net/specs/extensions/capability-negotiation.html#the-cap-req-subcommand)
|
// we send multiple REQ commands
|
||||||
if length+len(capability) >= 500 {
|
// (https://ircv3.net/specs/extensions/capability-negotiation.html#the-cap-req-subcommand)
|
||||||
recognizedCaps = append(recognizedCaps, capabilities)
|
if length+len(capability) >= 500 {
|
||||||
capabilities = make(irc.Capabilities, 0)
|
recognizedCaps = append(recognizedCaps, capabilities)
|
||||||
length = 0
|
capabilities = make(irc.Capabilities, 0)
|
||||||
}
|
length = 0
|
||||||
|
}
|
||||||
|
|
||||||
capabilities[key] = value
|
capabilities[key] = value
|
||||||
length += len(capability)
|
length += len(capability)
|
||||||
|
} else {
|
||||||
|
log.Println("Unsupported capability:", capability)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
log.Println("Unknown capability:", capability)
|
log.Println("Unknown capability:", capability)
|
||||||
}
|
}
|
||||||
@@ -266,6 +271,9 @@ func handleCAP(msg irc.Message, client *irc.Client) {
|
|||||||
}
|
}
|
||||||
client.SendCommand(commands.CAP, "END")
|
client.SendCommand(commands.CAP, "END")
|
||||||
case "ACK":
|
case "ACK":
|
||||||
|
if msg.Parameters[2] == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
caps := strings.Split(msg.Parameters[2], " ")
|
caps := strings.Split(msg.Parameters[2], " ")
|
||||||
for _, capability := range caps {
|
for _, capability := range caps {
|
||||||
if capability[0] == '-' {
|
if capability[0] == '-' {
|
||||||
@@ -342,6 +350,34 @@ func handleMYINFO(msg irc.Message, client *irc.Client) {
|
|||||||
client.Channels[0].AppendMsg(msg.Timestamp, info, msgOpts)
|
client.Channels[0].AppendMsg(msg.Timestamp, info, msgOpts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func handleISUPPORT(msg irc.Message, client *irc.Client) {
|
||||||
|
msgOpts := irc.MsgFmtOpts{
|
||||||
|
WithTimestamp: true,
|
||||||
|
AsServerMsg: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, token := range msg.Parameters[1 : len(msg.Parameters)-1] {
|
||||||
|
splits := strings.Split(token, "=")
|
||||||
|
key := splits[0]
|
||||||
|
value := ""
|
||||||
|
if len(splits) > 1 {
|
||||||
|
value = splits[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
if supported, known := commands.Features[key]; known {
|
||||||
|
if supported {
|
||||||
|
client.EnabledFeatures[key] = value
|
||||||
|
} else {
|
||||||
|
log.Printf("Unsupported feature: \"%v\" with value: \"%v\"\n", key, value)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Printf("Unknown feature: \"%v\" with value: \"%v\"\n", key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
client.Channels[0].AppendMsg(msg.Timestamp, token, msgOpts)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func handleLUSERCLIENT(msg irc.Message, client *irc.Client) {
|
func handleLUSERCLIENT(msg irc.Message, client *irc.Client) {
|
||||||
message := msg.Parameters[1]
|
message := msg.Parameters[1]
|
||||||
|
|
||||||
@@ -695,6 +731,8 @@ func HandleCommand(msg irc.Message, client *irc.Client) {
|
|||||||
handleCREATED(msg, client)
|
handleCREATED(msg, client)
|
||||||
case commands.RPL_MYINFO:
|
case commands.RPL_MYINFO:
|
||||||
handleMYINFO(msg, client)
|
handleMYINFO(msg, client)
|
||||||
|
case commands.RPL_ISUPPORT:
|
||||||
|
handleISUPPORT(msg, client)
|
||||||
case commands.RPL_LUSERCLIENT:
|
case commands.RPL_LUSERCLIENT:
|
||||||
handleLUSERCLIENT(msg, client)
|
handleLUSERCLIENT(msg, client)
|
||||||
case commands.RPL_LUSEROP:
|
case commands.RPL_LUSEROP:
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ package mainscreen
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"math"
|
"math"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/charmbracelet/bubbles/textinput"
|
"github.com/charmbracelet/bubbles/textinput"
|
||||||
@@ -194,6 +195,36 @@ func (s State) Update(msg tea.Msg) (State, tea.Cmd) {
|
|||||||
cmdsToProcess = append(cmdsToProcess, cmd)
|
cmdsToProcess = append(cmdsToProcess, cmd)
|
||||||
case InputBox:
|
case InputBox:
|
||||||
s.InputBox, cmd = s.InputBox.Update(msg)
|
s.InputBox, cmd = s.InputBox.Update(msg)
|
||||||
|
width := 0
|
||||||
|
|
||||||
|
command := strings.Split(s.InputBox.Input.Value(), " ")[0]
|
||||||
|
if len(command) > 0 && command[0] == '/' {
|
||||||
|
switch strings.ToUpper(command)[1:] {
|
||||||
|
case commands.AWAY:
|
||||||
|
w, err := strconv.ParseInt(s.Client.EnabledFeatures["AWAYLEN"], 10, 32)
|
||||||
|
if err == nil {
|
||||||
|
width = int(w)
|
||||||
|
}
|
||||||
|
case commands.TOPIC:
|
||||||
|
w, err := strconv.ParseInt(s.Client.EnabledFeatures["TOPICLEN"], 10, 32)
|
||||||
|
if err == nil {
|
||||||
|
width = int(w)
|
||||||
|
}
|
||||||
|
case commands.NICK:
|
||||||
|
w, err := strconv.ParseInt(s.Client.EnabledFeatures["NICKLEN"], 10, 32)
|
||||||
|
if err == nil {
|
||||||
|
width = int(w)
|
||||||
|
}
|
||||||
|
case commands.KICK:
|
||||||
|
w, err := strconv.ParseInt(s.Client.EnabledFeatures["KICKLEN"], 10, 32)
|
||||||
|
if err == nil {
|
||||||
|
width = int(w)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
s.InputBox.Input.CharLimit = width
|
||||||
|
|
||||||
cmdsToProcess = append(cmdsToProcess, cmd)
|
cmdsToProcess = append(cmdsToProcess, cmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user