<repo_name>terngkub/Frozen<file_sep>/simple_server.go
package main

import (
	"errors"
	"fmt"
	"log"
	"net"
	"os"
	"regexp"
	"strings"
)

const (
	CONN_HOST = "localhost"
	CONN_PORT = "3333"
	CONN_TYPE = "tcp"
)

type Account struct {
	Password string
	User     string
	Nickname string
}

type Channel struct {
	Name      string
	Topic     string
	Key       string
	AdminList []*Account
	UserList  []*Account
	BanList   []*Account
	UserMap   map[string]*Account
}

type Env struct {
	ChannelList []*Channel
	AccountList []*Account
	UserMap     map[string]*Account
	NicknameMap map[string]*Account
	ConnMap     map[string]net.Conn
	ChannelMap  map[string]*Channel
}

type Session struct {
	Env     *Env
	Conn    net.Conn
	Account *Account
}

func main() {
	ln, err := net.Listen(CONN_TYPE, CONN_HOST+":"+CONN_PORT)
	if err != nil {
		fmt.Println("Error listening :", err.Error())
		os.Exit(1)
	}
	defer ln.Close()
	env := Env{AccountList: []*Account{},
		ChannelList: []*Channel{},
		UserMap:     make(map[string]*Account),
		NicknameMap: make(map[string]*Account),
		ConnMap:     make(map[string]net.Conn),
		ChannelMap:  make(map[string]*Channel)}
	fmt.Println("Listening on ", CONN_HOST+":"+CONN_PORT)
	for {
		conn, err := ln.Accept()
		if err != nil {
			fmt.Println("Error accepting ", err.Error())
			continue
		}
		log.Println("accept connection", conn)
		go runSession(&env, conn)
	}
}

func runSession(env *Env, conn net.Conn) {
	session := Session{Env: env, Conn: conn, Account: nil}
	defer session.closeConnection()

	if !session.authorize() {
		return
	}
	defer session.disconnect()

	log.Println("ConnMap", session.Env.ConnMap)

	for {
		request, err := session.getRequest()
		if err != nil {
			break
		}
		session.handleRequest(request)
	}
}

func (session *Session) closeConnection() {
	log.Println("close session", session.Conn)
	session.Conn.Close()
}

func (session *Session) disconnect() {
	log.Println("disconnect", session.Account.Nickname)
	delete(session.Env.ConnMap, session.Account.Nickname)
}

func (session *Session) handleRequest(request string) {
	switch {
	case regexp.MustCompile(`^(:(.+) +)?NICK`).MatchString(request):
		session.changeNickname(request)
	case strings.HasPrefix(request, "PRIVMSG "):
		session.privateMSG(request)
	case strings.Contains(request, "JOIN"):
		session.joinChan(request)
	case strings.Contains(request, "PART"):
		session.leaveChan(request)
	case request == "NAMES":
		session.cmdNAMES()
	case request == "LIST":
		session.cmdLIST()
	}
}

func (session *Session) getRequest() (string, error) {
	request := make([]byte, 1024)
	len, err := session.Conn.Read(request)
	if err != nil {
		log.Println("Error reading: ", err)
		return "", err
	}
	if request[len-2] != '\r' || request[len-1] != '\n' {
		return "", errors.New("no CRLF")
	}
	requestStr := string(request[:len-2])
	fmt.Println("<" + requestStr + ">")
	return requestStr, nil
}

func (session *Session) cmdNAMES() {
	list := make([]string, len(session.Env.ConnMap))
	for key := range session.Env.ConnMap {
		list = append(list, key)
	}
	message := fmt.Sprintf("%s\r\n", list)
	session.Conn.Write([]byte(message))
}

func (session *Session) cmdLIST() {
	list := make([]string, len(session.Env.ChannelMap))
	for key := range session.Env.ChannelMap {
		list = append(list, key)
	}
	message := fmt.Sprintf("%s\r\n", list)
	session.Conn.Write([]byte(message))
}
<file_sep>/go.mod
module frozen

go 1.12
<file_sep>/validation.go
package main

import "regexp"

func isValidNickname(nick string) bool {
	r := regexp.MustCompile(`^[a-zA-Z0-9\-\[\]\\\x60\^\{\}]{1,8}$`)
	match := r.MatchString(nick)
	if match {
		return true
	}
	return false
}

func isValidUser(user string) bool {
	r := regexp.MustCompile(`[^\x20\x0\xd\xa]+`)
	match := r.MatchString(user)
	if match {
		return true
	}
	return false
}
<file_sep>/validation_test.go
package main

import "testing"

func TestNickname(t *testing.T) {
	t.Run("valid_00", nicknameAlpha)
	t.Run("valid_01", nicknameNumber)
	t.Run("valid_02", nicknameSymbol)
	t.Run("valid_03", nicknameCombine)
	t.Run("fail_00", nicknameTooLong)
	t.Run("fail_01", nicknameForbidden)
}

func nicknameAlpha(t *testing.T) {
	nick := "Nick"
	if !isValidNickname(nick) {
		t.Errorf("'%s' should be valid", nick)
	}
}

func nicknameNumber(t *testing.T) {
	nick := "0123"
	if !isValidNickname(nick) {
		t.Errorf("'%s' should be valid", nick)
	}
}

func nicknameSymbol(t *testing.T) {
	nick := "-[]\\`^{}"
	if !isValidNickname(nick) {
		t.Errorf("'%s' should be valid", nick)
	}
}

func nicknameCombine(t *testing.T) {
	nick := "[N0]{n\\}"
	if !isValidNickname(nick) {
		t.Errorf("'%s' should be valid", nick)
	}
}

func nicknameTooLong(t *testing.T) {
	nick := "ThisIsMyNickName"
	if isValidNickname(nick) {
		t.Errorf("'%s' shouldn't be valid", nick)
	}
}

func nicknameForbidden(t *testing.T) {
	nick := "OMG!!!"
	if isValidNickname(nick) {
		t.Errorf("'%s' shouldn't be valid", nick)
	}
}
<file_sep>/authorize.go
package main

import (
	"log"
	"strings"
)

func (session *Session) getRequests() ([]string, bool) {
	request := make([]byte, 512)
	len, err := session.Conn.Read(request)
	if err != nil {
		log.Println("error reading:", err)
		return []string{}, false
	}
	log.Printf("receive: '%s'", request)

	str := string(request[:len])

	if str[len-2] != '\r' || str[len-1] != '\n' {
		log.Println("error: request doesn't end with \\r\\n")
		return []string{}, true
	}
	trimmed := strings.Trim(str, "\r\n")
	splitted := strings.Split(trimmed, "\r\n")
	return splitted, true
}

func (session *Session) authorize() bool {
	account, ok := session.getAccountData()
	if !ok {
		return false
	}
	if oldAccount, ok := session.Env.UserMap[account.User]; ok {
		return session.login(account, oldAccount)
	}
	return session.register(account)
}

func (session *Session) getAccountData() (*Account, bool) {
	account := Account{}
	for !account.isComplete() {
		requests, ok := session.getRequests()
		if !ok {
			return nil, false
		}
		for _, request := range requests {
			switch {
			case strings.HasPrefix(request, "PASS"):
				if newPass := session.cmdPASS(request); newPass != "" {
					account.Password = newPass
				}
			case strings.HasPrefix(request, "NICK"):
				if newNick := session.cmdNICK(request); newNick != "" {
					account.Nickname = newNick
				}
			case strings.HasPrefix(request, "USER"):
				if newUser := session.cmdUSER(request); newUser != "" {
					account.User = newUser
					if account.Password != "" {
						if _, ok := session.Env.UserMap[account.User]; ok {
							return &account, true
						}
					}
				}
			default:
				session.error451()
				continue
			}
			if account.isComplete() {
				break
			}
		}
	}
	return &account, true
}

func (account *Account) isComplete() bool {
	if account.Password != "" && account.Nickname != "" && account.User != "" {
		return true
	}
	return false
}

func (session *Session) cmdPASS(request string) string {
	if session.Account != nil {
		session.error462()
		return ""
	}
	matches := doRegexpSubmatch("^PASS +(.+)$", request)
	if len(matches) != 2 {
		session.error461("PASS")
		return ""
	}
	log.Println("parse PASS:", matches[1])
	return matches[1]
}

func (session *Session) cmdNICK(request string) string {
	matches := doRegexpSubmatch("^NICK +(.+?)(?: +(?:.+)){0,1}$", request)
	if len(matches) != 2 {
		session.error431()
		return ""
	}
	if !isValidNickname(matches[1]) {
		session.error432(matches[1])
		return ""
	}
	log.Println("parse NICK:", matches[1])
	return matches[1]
}

func (session *Session) cmdUSER(request string) string {
	if session.Account != nil {
		session.error462()
		return ""
	}
	matches := doRegexpSubmatch("^USER +(.+) +.+ +.+ +:.+$", request)
	if len(matches) != 2 {
		session.error461("USER")
		return ""
	}
	log.Println("parse USER:", matches[1])
	return matches[1]
}

func (session *Session) login(newAccount *Account, oldAccount *Account) bool {
	log.Println("attemp to login", session.Conn)
	if newAccount.Password != oldAccount.Password {
		session.error464()
		return false
	}
	session.Account = oldAccount
	session.Env.ConnMap[oldAccount.Nickname] = session.Conn
	log.Println("login success", session.Conn)
	session.welcome()
	return true
}

func (session *Session) register(account *Account) bool {
	log.Println("attemp to register", session.Conn)
	if _, isDuplicated := session.Env.NicknameMap[account.Nickname]; isDuplicated {
		session.error434(account.Nickname)
		return false
	}
	session.Env.AccountList = append(session.Env.AccountList, account)
	length := len(session.Env.AccountList)
	last := &session.Env.AccountList[length-1]
	session.Account = *last
	session.Env.UserMap[account.User] = *last
	session.Env.NicknameMap[account.Nickname] = *last
	session.Env.ConnMap[account.Nickname] = session.Conn
	log.Println("register success", session.Conn)
	session.welcome()
	return true
}

func (session *Session) changeNickname(request string) {
	matches := doRegexpSubmatch("^(?::(?:.+) +)?NICK +(.+)$", request)
	if len(matches) != 2 {
		session.error431()
		return
	}
	if !isValidNickname(matches[1]) {
		session.error432(matches[1])
		return
	}
	if _, isDuplicated := session.Env.NicknameMap[matches[1]]; isDuplicated {
		session.error433(matches[1])
		return
	}
	session.Env.NicknameMap[matches[1]] = session.Account
	session.Env.ConnMap[matches[1]] = session.Conn
	delete(session.Env.NicknameMap, session.Account.Nickname)
	delete(session.Env.ConnMap, session.Account.Nickname)
	oldNickname := session.Account.Nickname
	session.Account.Nickname = matches[1]
	log.Printf("change nickname from %s to %s\n", oldNickname, session.Account.Nickname)
}
<file_sep>/utils.go
package main

import "regexp"

func doRegexpSubmatch(format, str string) []string {
	r := regexp.MustCompile(format)
	matches := r.FindStringSubmatch(str)
	return matches
}

func remove_user(s []*Account, i int) []*Account {
	if len(s) <= 1 {
		return nil
	}
	s[len(s)-1], s[i] = s[i], s[len(s)-1]
	return s[:len(s)-1]
}

func remove_chan(s []*Channel, i int) []*Channel {
	if len(s) <= 1 {
		return nil
	}
	s[len(s)-1], s[i] = s[i], s[len(s)-1]
	return s[:len(s)-1]
}
<file_sep>/messaging.go
package main

import (
	"fmt"
	"strings"
)

func (session *Session) privateMSG(request string) {
	matches := doRegexpSubmatch("^PRIVMSG +(.+?) +:(.+)$", request)
	if len(matches) != 3 {
		session.error461("PRIVMSG")
		return
	}

	msg := fmt.Sprintf(":%s!%s@%s PRIVMSG %s :%s\r\n",
		session.Account.Nickname,
		session.Account.User,
		CONN_HOST,
		matches[1],
		matches[2])

	if matches[1][0] == '#' || matches[1][0] == '&' {
		if channel, ok := session.Env.ChannelMap[matches[1]]; ok {
			for _, account := range channel.UserList {
				if account.Nickname != session.Account.Nickname {
					dstConn := session.Env.ConnMap[account.Nickname]
					dstConn.Write([]byte(msg))
				}
			}
		} else {
			session.error401(matches[1])
		}
	} else {
		if account, ok := session.Env.NicknameMap[matches[1]]; ok {
			dstConn := session.Env.ConnMap[account.Nickname]
			dstConn.Write([]byte(msg))
		} else {
			session.error401(matches[1])
		}
	}
}

func (session *Session) joinChan(request string) {
	src_user := session.Account
	var req_keys []string
	sp_matches := strings.Split(request, " ")
	// grab channels and keys from request
	if len(sp_matches) >= 2 {
		req_chans := strings.Split(sp_matches[1], ",")
		if len(sp_matches) >= 3 {
			req_keys = strings.Split(sp_matches[2], ",")
		}
		for i, req_chan := range req_chans {
			channel, ok := session.Env.ChannelMap[req_chan]
			if ok == true {
				if is_banned(src_user, *channel) == false {
					// check key
					session.checkChan(channel, req_keys, i)
				}
			} else if len(req_keys) > i && len(req_keys[i]) > 0 {
				session.createChannel(req_chans[i], "", req_keys[i])
			} else {
				session.createChannel(req_chans[i], "", "")
			}
		}
	} else {
		session.error461(request)
	}
}

func (session *Session) checkChan(channel *Channel, req_keys []string, idx int) {
	if channel.Key != "" {
		if len(req_keys) > idx && len(req_keys[idx]) > 0 {
			if req_keys[idx] == channel.Key {
				session.append_user(channel)
			}
		}
	} else {
		session.append_user(channel)
	}
	//TODO message if banned

}

func is_banned(user *Account, channel Channel) bool {
	for _, banned := range channel.BanList {
		if user.Nickname == banned.Nickname {
			return true
		}
	}
	return false
}

func (session *Session) createChannel(name string, topic string, key string) {
	new_chan := Channel{
		Name:      name,
		Topic:     topic,
		Key:       key,
		AdminList: []*Account{session.Account},
		UserList:  []*Account{},
		BanList:   []*Account{},
		UserMap:   make(map[string]*Account)}
	session.Env.ChannelList = append(session.Env.ChannelList, &new_chan)
	session.Env.ChannelMap[name] = &new_chan
	session.append_user(&new_chan)
}

func (session *Session) append_user(channel *Channel) {
	// add user to channel
	src_user := session.Account
	channel.UserList = append(channel.UserList, src_user)
	channel.UserMap[src_user.Nickname] = src_user
	// alert other users
	for _, user := range channel.UserList {
		alert_message := fmt.Sprintf("%s!%s@%s %s %s\r\n",
			src_user.Nickname,
			src_user.User,
			CONN_HOST,
			"JOIN",
			channel.Name)
		session.Env.ConnMap[user.Nickname].Write([]byte(alert_message))
	}
	//send responses
	topic_message := fmt.Sprintf(":%s!%s@%s %s %s %s :%s\r\n",
		src_user.Nickname,
		src_user.User,
		CONN_HOST,
		"332",
		src_user.Nickname,
		channel.Name,
		channel.Topic)
	session.Conn.Write([]byte(topic_message))
	var users_list string
	for _, user := range channel.UserList {
		users_list += user.Nickname + " "
	}
	names_message := fmt.Sprintf(":%s!%s@%s %s %s = %s :%s\r\n",
		src_user.Nickname,
		src_user.User,
		CONN_HOST,
		"353",
		src_user.Nickname,
		channel.Name,
		users_list)
	session.Conn.Write([]byte(names_message))
	end_names_message := fmt.Sprintf(":%s!%s@%s %s %s %s :%s\r\n",
		src_user.Nickname,
		src_user.User,
		CONN_HOST,
		"366",
		src_user.Nickname,
		channel.Name,
		"End of NAMES list")
	session.Conn.Write([]byte(end_names_message))
}

func (session *Session) leaveChan(request string) {
	if len(request) > 5 {
		src := session.Account
		matches := doRegexpSubmatch("PART (.*) :(.*)", request)
		if len(matches) > 0 {
			// if channel/user exists
			channel, ok1 := session.Env.ChannelMap[matches[1]]
			if ok1 == true {
				_, ok2 := channel.UserMap[src.Nickname]
				if ok2 == true {
					session.sendPart(request, src, channel)
				}
			}
		}
	}
}
func (session *Session) sendPart(request string, src *Account, channel *Channel) {
	// send PART messages
	i := strings.Index(request[1:], ":")
	msg := fmt.Sprintf(":%s!%s@%s PART %s :%s\r\n",
		src.Nickname,
		src.User,
		CONN_HOST,
		channel.Name,
		request[i+2:])
	for _, user := range channel.UserList {
		if user.Nickname != src.Nickname {
			dst_conn := session.Env.ConnMap[user.Nickname]
			dst_conn.Write([]byte(msg))
		}
	}
	// leave chann
	for i, user := range channel.UserList {
		if user.Nickname == src.Nickname {
			channel.UserList = remove_user(channel.UserList, i)
			delete(channel.UserMap, user.Nickname)
		}
	}
	// remove empty channel
	if channel.UserList == nil {
		for i, channel := range session.Env.ChannelList {
			if channel.Name == channel.Name {
				session.Env.ChannelList = remove_chan(session.Env.ChannelList, i)
				delete(session.Env.ChannelMap, channel.Name)
			}
		}
	}
}
<file_sep>/reply.go
package main

import (
	"fmt"
	"log"
)

func (session *Session) reply(message string) {
	session.Conn.Write([]byte(message))
	log.Printf("reply: '%s'", message)
}

func (session *Session) welcome() {
	message := fmt.Sprintf(":%s 001 %s :Welcome to the Internet Relay Network\r\n", CONN_HOST, session.Account.Nickname)
	session.reply(message)
}

func (session *Session) error401(name string) {
	message := fmt.Sprintf(":%s 401 %s :No such nick/channel\r\n", CONN_HOST, name)
	session.reply(message)
}

func (session *Session) error431() {
	message := fmt.Sprintf(":%s 431 :No nickname is given\r\n", CONN_HOST)
	session.reply(message)
}

func (session *Session) error432(nick string) {
	message := fmt.Sprintf(":%s 432 %s :Erroneus nickname\r\n", CONN_HOST, nick)
	session.reply(message)
}

func (session *Session) error433(nick string) {
	message := fmt.Sprintf(":%s 433 * %s :Nickname collision KILL\r\n", CONN_HOST, nick)
	session.reply(message)
}

func (session *Session) error434(nick string) {
	message := fmt.Sprintf(":%s 434 * %s :Nickname is already in use\r\n", CONN_HOST, nick)
	session.reply(message)
}

// Authorization

func (session *Session) error451() {
	message := fmt.Sprintf(":%s 451 :You have not registered\r\n", CONN_HOST)
	session.reply(message)
}

func (session *Session) error461(command string) {
	message := fmt.Sprintf(":%s 461 %s :Not enough parameters\r\n", CONN_HOST, command)
	session.reply(message)
}

func (session *Session) error462() {
	message := fmt.Sprintf(":%s 462 :You may not reregister\r\n", CONN_HOST)
	session.reply(message)
}

func (session *Session) error464() {
	message := fmt.Sprintf(":%s 464 :Password incorrect\r\n", CONN_HOST)
	session.reply(message)
}
