package irc import ( "bytes" "fmt" "github.com/google/uuid" "github.com/sirupsen/logrus" "net" "strings" ) func newIrcClient(conn net.Conn, log *logrus.Logger, mc chan *ircMessage, s *ircd) *ircClient { c := new(ircClient) c.Id = uuid.New().String() c.conn = conn c.log = log c.mc = mc c.server = s c.inputBytes = new(bytes.Buffer) c.outputBytes = new(bytes.Buffer) c.session = NewIrcUserSession() return c } type ircClient struct { //FIXME add a mutex and protect during writes Id string conn net.Conn log *logrus.Logger session *ircUserSession inputBytes *bytes.Buffer outputBytes *bytes.Buffer mc chan *ircMessage server *ircd } func (c *ircClient) ServerName() string { return c.server.serverName } func (c *ircClient) Kill() { //server will remove it from the server-managed client list //and will call c.CleanupAndClose() when it pops from this channel c.server.deadClients <- c } func (c *ircClient) CleanupAndClose() { //client cleanup //FIXME truncate client buffers and whatnot here // server object already handles removing it from the list of clients // for the server, so don't do it here c.log.Infof("client=%s cleanup", c.Id) c.conn.Close() } func (c *ircClient) AppendStringToOutputBuffer(input string) { // woo boilerplate because no polymorphism c.AppendBytesToOutputBuffer([]byte(input)) } func (c *ircClient) AppendBytesToOutputBuffer(input []byte) { c.outputBytes.Write(input) } func (c *ircClient) FlushOutputBuffer() bool { numBytes, err := c.outputBytes.WriteTo(c.conn) if err != nil { c.log.Debugf("failed to write completely to client<%s>, marking dead", c.Id) c.Kill() return false } else { c.log.Debugf("wrote %d bytes to client<%s>", numBytes, c.Id) return true } } func (c *ircClient) RespUnknownCommand(command string) { //421 == unknown command c.SendServerMessage(421, []string{command, "Unknown command. Please try your call again later."}) } func (c *ircClient) SendServerMessage(code uint16, params []string) bool { //FIXME this is terrible hacky duct tape code, redo it p := "" if len(params) > 0 { // pop off last item for separate processing lastelem := params[len(params)-1] params = params[:len(params)-1] for _, elem := range params { if strings.ContainsAny(elem, " \t\r\n") { c.log.Errorf("wtf %s", elem) return false } } if strings.ContainsAny(lastelem, "\t\r\n") { c.log.Errorf("wtf %s", lastelem) return false } if strings.ContainsAny(lastelem, " ") { lastelem = fmt.Sprintf(":%s", lastelem) } p = strings.Join(params, " ") if len(p) > 0 { p = " " + lastelem } else { p = lastelem } } sendstr := fmt.Sprintf(":%s %d %s", c.ServerName(), code, c.session.nick) if len(p) > 0 { sendstr = sendstr + " " + p } sendstr = sendstr + "\r\n" c.log.Debugf("sending string '%s' to client<%s>", sendstr, c.Id) c.AppendStringToOutputBuffer(sendstr) return c.FlushOutputBuffer() } func (c *ircClient) AppendInputBuffer(input []byte) { // Read the incoming connection into the buffer. c.log.Printf("conn<%s>: got %d bytes from net", c.Id, len(input)) c.inputBytes.Write(input) c.log.Debugf("conn<%s> buffer now %d bytes total", c.Id, c.inputBytes.Len()) c.ParseInputBuffer() } func (c *ircClient) ParseInputBuffer() { //FIXME update a timer here on the client when a line is parsed //successfully and time them out after a while c.log.Debugf("my input buffer is %d bytes", c.inputBytes.Len()) c.log.Debugf("my input buffer is: '%s'", c.inputBytes.String()) line, err := c.inputBytes.ReadString(byte('\n')) if err == nil { c.mc <- c.ParseSingleInput(line) c.ParseInputBuffer() } else { // error parsing input buffer, probably don't have a full line yet return } c.log.Debugf("my input buffer is %d bytes", c.inputBytes.Len()) c.log.Debugf("my input buffer is: '%s'", c.inputBytes.String()) } func (c *ircClient) ParseSingleInput(line string) *ircMessage { return NewIrcMessageFromString(line, c) }