Exemplo n.º 1
1
// GetJoinHandle implements the handle for the join document (GET).
func GetJoinHandle(newConn net.Conn, roomName, httpPayload string, rooms *config.CherryRooms, preprocessor *html.Preprocessor) {
	//  INFO(Santiago): The form for room joining was requested, so we will flush it to client.
	var replyBuffer []byte
	replyBuffer = rawhttp.MakeReplyBuffer(preprocessor.ExpandData(roomName, rooms.GetEntranceTemplate(roomName)), 200, true)
	newConn.Write(replyBuffer)
	newConn.Close()
}
Exemplo n.º 2
0
// PubHandle implements the handle for the room's public directory (GET).
func PubHandle(newConn net.Conn, roomName, httpPayload string, rooms *config.CherryRooms, preprocessor *html.Preprocessor) {
	pubdir := rooms.GetPublicDirectory(roomName)
	var httpReq string
	var spaceNr int
	for _, h := range httpPayload {
		if h == ' ' {
			spaceNr++
		}
		if h == '\n' || h == '\r' || spaceNr > 1 {
			break
		}
		httpReq += string(h)
	}
	var replyBuffer []byte
	if len(pubdir) == 0 || !strings.HasPrefix(httpReq, "GET /pub/"+pubdir) {
		replyBuffer = rawhttp.MakeReplyBuffer(html.GetBadAssErrorData(), 404, true)
	} else {
		relativeLocalPath := httpReq[9:]
		_, err := os.Stat(relativeLocalPath)
		if os.IsNotExist(err) {
			replyBuffer = rawhttp.MakeReplyBuffer(html.GetBadAssErrorData(), 404, true)
		} else {
			replyBuffer = rawhttp.MakeReplyBufferByFilePath(relativeLocalPath, 200, true)
		}
	}
	newConn.Write(replyBuffer)
	newConn.Close()
}
Exemplo n.º 3
0
// GetRoomTemplates parses "cherry.[roomName].templates" section.
func GetRoomTemplates(roomName string, cherryRooms *config.CherryRooms, configData, filepath string) *CherryFileError {
	var data string
	var line int
	var err *CherryFileError
	data, _, line, err = GetDataFromSection("cherry."+roomName+".templates",
		configData, 1, filepath)
	if err != nil {
		return err
	}
	var set []string
	set, line, data = GetNextSetFromData(data, line, "=")
	for len(set) == 2 {
		if cherryRooms.HasTemplate(roomName, set[0]) {
			return NewCherryFileError(filepath, line, "room template \""+set[0]+"\" redeclared.")
		}
		if len(set[1]) == 0 {
			return NewCherryFileError(filepath, line, "room template with no value.")
		}
		if set[1][0] != '"' || set[1][len(set[1])-1] != '"' {
			return NewCherryFileError(filepath, line, "room template must be set with a valid string.")
		}
		var templateData []byte
		var templateDataErr error
		templateData, templateDataErr = ioutil.ReadFile(set[1][1 : len(set[1])-1])
		if templateDataErr != nil {
			return NewCherryFileError(filepath, line, "unable to access room template file [more details: "+templateDataErr.Error()+"].")
		}
		cherryRooms.AddTemplate(roomName, set[0], string(templateData))
		set, line, data = GetNextSetFromData(data, line, "=")
	}
	return nil
}
Exemplo n.º 4
0
// GetBriefHandle implements the handle for the brief document (GET).
func GetBriefHandle(newConn net.Conn, roomName, httpPayload string, rooms *config.CherryRooms, preprocessor *html.Preprocessor) {
	var replyBuffer []byte
	if rooms.IsAllowingBriefs(roomName) {
		replyBuffer = rawhttp.MakeReplyBuffer(preprocessor.ExpandData(roomName, rooms.GetBriefTemplate(roomName)), 200, true)
	} else {
		replyBuffer = rawhttp.MakeReplyBuffer(html.GetBadAssErrorData(), 404, true)
	}
	newConn.Write(replyBuffer)
	newConn.Close()
}
Exemplo n.º 5
0
func roomImageMainVerifier(mSet, sSet []string, mLine, sLine int, roomName, filepath string, cherryRooms *config.CherryRooms) *CherryFileError {
	if cherryRooms.HasImage(roomName, mSet[0]) {
		return NewCherryFileError(filepath, mLine, "room image \""+mSet[0]+"\" redeclared.")
	}
	if len(mSet[1]) == 0 {
		return NewCherryFileError(filepath, mLine, "unlabeled room image.")
	}
	if mSet[1][0] != '"' || mSet[1][len(mSet[1])-1] != '"' {
		return NewCherryFileError(filepath, mLine, "room image must be set with a valid string.")
	}
	return nil
}
Exemplo n.º 6
0
// GetTopHandle implements the handle for the top document (GET).
func GetTopHandle(newConn net.Conn, roomName, httpPayload string, rooms *config.CherryRooms, preprocessor *html.Preprocessor) {
	var userData map[string]string
	userData = rawhttp.GetFieldsFromGet(httpPayload)
	var replyBuffer []byte
	if !rooms.IsValidUserRequest(roomName, userData["user"], userData["id"], newConn) {
		replyBuffer = rawhttp.MakeReplyBuffer(html.GetBadAssErrorData(), 404, true)
	} else {
		replyBuffer = rawhttp.MakeReplyBuffer(preprocessor.ExpandData(roomName, rooms.GetTopTemplate(roomName)), 200, true)
	}
	newConn.Write(replyBuffer)
	newConn.Close()
}
Exemplo n.º 7
0
// GetExitHandle implements the handle for the exit document (GET).
func GetExitHandle(newConn net.Conn, roomName, httpPayload string, rooms *config.CherryRooms, preprocessor *html.Preprocessor) {
	var userData map[string]string
	var replyBuffer []byte
	userData = rawhttp.GetFieldsFromGet(httpPayload)
	if !rooms.IsValidUserRequest(roomName, userData["user"], userData["id"], newConn) {
		replyBuffer = rawhttp.MakeReplyBuffer(html.GetBadAssErrorData(), 404, true)
	} else {
		preprocessor.SetDataValue("{{.nickname}}", userData["user"])
		preprocessor.SetDataValue("{{.session-id}}", userData["id"])
		replyBuffer = rawhttp.MakeReplyBuffer(preprocessor.ExpandData(roomName, rooms.GetExitTemplate(roomName)), 200, true)
	}
	rooms.EnqueueMessage(roomName, userData["user"], "", "", "", rooms.GetExitMessage(roomName), "")
	newConn.Write(replyBuffer)
	rooms.RemoveUser(roomName, userData["user"])
	newConn.Close()
}
Exemplo n.º 8
0
func openRooms(configPath string) {
	var cherryRooms *config.CherryRooms
	var err *parser.CherryFileError
	cherryRooms, err = parser.ParseCherryFile(configPath)
	if err != nil {
		fmt.Println(err.Error())
		os.Exit(1)
	} else {
		rooms := cherryRooms.GetRooms()
		for _, r := range rooms {
			go messageplexer.RoomMessagePlexer(r, cherryRooms)
			go peer(r, cherryRooms)
		}
	}
	sigintWatchdog := make(chan os.Signal, 1)
	signal.Notify(sigintWatchdog, os.Interrupt)
	signal.Notify(sigintWatchdog, syscall.SIGINT|syscall.SIGTERM)
	<-sigintWatchdog
	cleanup()
}
Exemplo n.º 9
0
func peer(roomName string, c *config.CherryRooms) {
	port := c.GetListenPort(roomName)
	var portNum int64
	portNum, _ = strconv.ParseInt(port, 10, 16)
	var err error
	var room *config.RoomConfig
	room = c.GetRoomByPort(int16(portNum))
	//room.MainPeer, err = net.Listen("tcp", c.GetServerName()+":"+port)
	room.MainPeer, err = getListenPeer(c, port)
	if err != nil {
		fmt.Println("ERROR: " + err.Error())
		os.Exit(1)
	}
	defer room.MainPeer.Close()
	for {
		conn, err := room.MainPeer.Accept()
		if err != nil {
			fmt.Println(err.Error())
			continue
		}
		go processNewConnection(conn, roomName, c)
	}
}
Exemplo n.º 10
0
func setMaxUsers(cherryRooms *config.CherryRooms, roomName, value string) {
	var intValue int64
	intValue, _ = strconv.ParseInt(value, 10, 64)
	cherryRooms.SetMaxUsers(roomName, int(intValue))
}
Exemplo n.º 11
0
func setPrivateMessageMarker(cherryRooms *config.CherryRooms, roomName, marker string) {
	cherryRooms.SetPrivateMessageMarker(roomName, marker[1:len(marker)-1])
}
Exemplo n.º 12
0
func setGreetingMessage(cherryRooms *config.CherryRooms, roomName, message string) {
	cherryRooms.SetGreetingMessage(roomName, message[1:len(message)-1])
}
Exemplo n.º 13
0
func setOnDeIgnoreMessage(cherryRooms *config.CherryRooms, roomName, message string) {
	cherryRooms.SetOnDeIgnoreMessage(roomName, message[1:len(message)-1])
}
Exemplo n.º 14
0
func setDeIgnoreAction(cherryRooms *config.CherryRooms, roomName, action string) {
	cherryRooms.SetDeIgnoreAction(roomName, action[1:len(action)-1])
}
Exemplo n.º 15
0
func getListenPeer(c *config.CherryRooms, port string) (net.Listener, error) {
	var listenConn net.Listener
	var listenError error
	if c.GetCertificatePath() != "" && c.GetPrivateKeyPath() != "" {
		cert, err := tls.LoadX509KeyPair(c.GetCertificatePath(), c.GetPrivateKeyPath())
		if err != nil {
			return nil, err
		}
		secParams := &tls.Config{Certificates: []tls.Certificate{cert}}
		listenConn, listenError = tls.Listen("tcp", c.GetServerName()+":"+port, secParams)
	} else {
		listenConn, listenError = net.Listen("tcp", c.GetServerName()+":"+port)
	}
	return listenConn, listenError
}
Exemplo n.º 16
0
// RoomMessagePlexer performs all message delivering stuff.
func RoomMessagePlexer(roomName string, rooms *config.CherryRooms) {
	preprocessor := html.NewHTMLPreprocessor(rooms)
	var allUsers = rooms.GetAllUsersAlias(roomName)
	for {
		runtime.Gosched()
		currMessage := rooms.GetNextMessage(roomName)
		if len(currMessage.Say) == 0 && len(currMessage.Image) == 0 /*&& len(currMessage.Sound) == 0*/ {
			continue
		}
		var actionTemplate string
		if rooms.HasAction(roomName, currMessage.Action) {
			actionTemplate = rooms.GetRoomActionTemplate(roomName, currMessage.Action)
		}
		if len(actionTemplate) == 0 {
			actionTemplate = "<p>({{.hour}}:{{.minute}}:{{.second}}) <b>{{.message-colored-user}}</b>: {{.message-says}}" //  INFO(Santiago): A very basic action template.
		}
		message := preprocessor.ExpandData(roomName, actionTemplate)
		if currMessage.Priv != "1" {
			rooms.AddPublicMessage(roomName, message)
		}
		preprocessor.SetDataValue("{{.current-formatted-message}}", message)
		messageHighlighted := preprocessor.ExpandData(roomName, rooms.GetHighlightTemplate(roomName))
		preprocessor.UnsetDataValue("{{.current-formatted-message}}")
		users := rooms.GetRoomUsers(roomName)
		for _, user := range users {
			if currMessage.Priv == "1" &&
				user != currMessage.From &&
				user != currMessage.To &&
				currMessage.To != allUsers {
				continue
			}
			if rooms.IsIgnored(user, currMessage.From, roomName) {
				continue
			}
			var messageBuffer []byte
			if user == currMessage.From ||
				user == currMessage.To {
				messageBuffer = []byte(messageHighlighted)
			} else {
				messageBuffer = []byte(message)
			}
			var conn net.Conn
			conn = rooms.GetUserConnection(roomName, user)
			if conn == nil {
				continue
			}
			_, e := conn.Write(messageBuffer)
			if e != nil {
				rooms.EnqueueMessage(roomName, user, "", "", "", rooms.GetExitMessage(roomName), "")
				rooms.RemoveUser(roomName, user)
			}
		}
		rooms.DequeueMessage(roomName)
	}
}
Exemplo n.º 17
0
func setAllUsersAlias(cherryRooms *config.CherryRooms, roomName, value string) {
	cherryRooms.SetAllUsersAlias(roomName, value[1:len(value)-1])
}
Exemplo n.º 18
0
func setAllowBrief(cherryRooms *config.CherryRooms, roomName, value string) {
	var allow bool
	allow = (value == "yes" || value == "true")
	cherryRooms.SetAllowBrief(roomName, allow)
}
Exemplo n.º 19
0
// PostJoinHandle implements the handle for the join document (POST).
func PostJoinHandle(newConn net.Conn, roomName, httpPayload string, rooms *config.CherryRooms, preprocessor *html.Preprocessor) {
	//  INFO(Santiago): Here, we need firstly parse the posted fields, check for "nickclash", if this is the case
	//                  flush the page informing it. Otherwise we add the user basic info and flush the room skeleton
	//                  [TOP/BODY/BANNER]. Then we finally close the connection.
	var userData map[string]string
	var replyBuffer []byte
	userData = rawhttp.GetFieldsFromPost(httpPayload)
	if _, posted := userData["user"]; !posted {
		newConn.Close()
		return
	}
	if _, posted := userData["color"]; !posted {
		newConn.Close()
		return
	}
	preprocessor.SetDataValue("{{.nickname}}", userData["user"])
	preprocessor.SetDataValue("{{.session-id}}", "0")
	if rooms.HasUser(roomName, userData["user"]) || userData["user"] == rooms.GetAllUsersAlias(roomName) ||
		strings.Contains(userData["user"], "<") || strings.Contains(userData["user"], ">") ||
		strings.Contains(userData["user"], "&lt") || strings.Contains(userData["user"], "&gt") {
		replyBuffer = rawhttp.MakeReplyBuffer(preprocessor.ExpandData(roomName, rooms.GetNickclashTemplate(roomName)), 200, true)
	} else {
		rooms.AddUser(roomName, userData["user"], userData["color"], true)
		preprocessor.SetDataValue("{{.session-id}}", rooms.GetSessionID(userData["user"], roomName))
		replyBuffer = rawhttp.MakeReplyBuffer(preprocessor.ExpandData(roomName, rooms.GetSkeletonTemplate(roomName)), 200, true)
		rooms.EnqueueMessage(roomName, userData["user"], "", "", "", rooms.GetJoinMessage(roomName), "")
	}
	newConn.Write(replyBuffer)
	newConn.Close()
}
Exemplo n.º 20
0
// PostFindHandle implements the handle for the find document (POST).
func PostFindHandle(newConn net.Conn, roomName, httpPayload string, rooms *config.CherryRooms, preprocessor *html.Preprocessor) {
	var userData map[string]string
	userData = rawhttp.GetFieldsFromPost(httpPayload)
	var replyBuffer []byte
	if _, posted := userData["user"]; !posted {
		replyBuffer = rawhttp.MakeReplyBuffer(html.GetBadAssErrorData(), 404, true)
	} else {
		var result string
		result = preprocessor.ExpandData(roomName, rooms.GetFindResultsHeadTemplate(roomName))
		listing := rooms.GetFindResultsBodyTemplate(roomName)
		availRooms := rooms.GetRooms()
		user := strings.ToUpper(userData["user"])
		if len(user) > 0 {
			for _, r := range availRooms {
				users := rooms.GetRoomUsers(r)
				preprocessor.SetDataValue("{{.find-result-users-total}}", rooms.GetUsersTotal(r))
				preprocessor.SetDataValue("{{.find-result-room-name}}", r)
				for _, u := range users {
					if strings.HasPrefix(strings.ToUpper(u), user) {
						preprocessor.SetDataValue("{{.find-result-user}}", u)
						result += preprocessor.ExpandData(roomName, listing)
					}
				}
			}
		}
		result += preprocessor.ExpandData(roomName, rooms.GetFindResultsTailTemplate(roomName))
		replyBuffer = rawhttp.MakeReplyBuffer(result, 200, true)
	}
	newConn.Write(replyBuffer)
	newConn.Close()
}
Exemplo n.º 21
0
func setPublicDirectory(cherryRooms *config.CherryRooms, roomName, value string) {
	cherryRooms.SetPublicDirectory(roomName, value[1:len(value)-1])
}
Exemplo n.º 22
0
// PostBannerHandle implements the handle for the banner document (POST).
func PostBannerHandle(newConn net.Conn, roomName, httpPayload string, rooms *config.CherryRooms, preprocessor *html.Preprocessor) {
	var userData map[string]string
	var replyBuffer []byte
	var invalidRequest = false
	userData = rawhttp.GetFieldsFromPost(httpPayload)
	if _, has := userData["user"]; !has {
		invalidRequest = true
	} else if _, has := userData["id"]; !has {
		invalidRequest = true
	} else if _, has := userData["action"]; !has {
		invalidRequest = true
	} else if _, has := userData["whoto"]; !has {
		invalidRequest = true
	} else if _, has := userData["image"]; !has {
		invalidRequest = true
	} else if _, has := userData["says"]; !has {
		invalidRequest = true
	}
	var restoreBanner = true
	if invalidRequest || !rooms.IsValidUserRequest(roomName, userData["user"], userData["id"], newConn) {
		replyBuffer = rawhttp.MakeReplyBuffer(html.GetBadAssErrorData(), 404, true)
	} else if userData["action"] == rooms.GetIgnoreAction(roomName) {
		if userData["user"] != userData["whoto"] && !rooms.IsIgnored(userData["user"], userData["whoto"], roomName) {
			rooms.AddToIgnoreList(userData["user"], userData["whoto"], roomName)
			rooms.EnqueueMessage(roomName, userData["user"], "", "", "", rooms.GetOnIgnoreMessage(roomName)+userData["whoto"], "1")
			restoreBanner = false
		}
	} else if userData["action"] == rooms.GetDeIgnoreAction(roomName) {
		if rooms.IsIgnored(userData["user"], userData["whoto"], roomName) {
			rooms.DelFromIgnoreList(userData["user"], userData["whoto"], roomName)
			rooms.EnqueueMessage(roomName, userData["user"], "", "", "", rooms.GetOnDeIgnoreMessage(roomName)+userData["whoto"], "1")
			restoreBanner = false
		}
	} else {
		var somethingToSay = (len(userData["says"]) > 0 || len(userData["image"]) > 0 || len(userData["sound"]) > 0)
		if somethingToSay {
			//  INFO(Santiago): Any further antiflood control would go from here.
			rooms.EnqueueMessage(roomName, userData["user"], userData["whoto"], userData["action"], userData["image"], userData["says"], userData["priv"])
		}
	}
	preprocessor.SetDataValue("{{.nickname}}", userData["user"])
	preprocessor.SetDataValue("{{.session-id}}", userData["id"])
	if userData["priv"] == "1" {
		preprocessor.SetDataValue("{{.priv}}", "checked")
	}
	tempBanner := preprocessor.ExpandData(roomName, rooms.GetBannerTemplate(roomName))
	if restoreBanner {
		tempBanner = strings.Replace(tempBanner,
			"<option value = \""+userData["whoto"]+"\">",
			"<option value = \""+userData["whoto"]+"\" selected>", -1)
		tempBanner = strings.Replace(tempBanner,
			"<option value = \""+userData["action"]+"\">",
			"<option value = \""+userData["action"]+"\" selected>", -1)
	}
	replyBuffer = rawhttp.MakeReplyBuffer(tempBanner, 200, true)
	newConn.Write(replyBuffer)
	newConn.Close()
}
Exemplo n.º 23
0
func roomActionSetter(cherryRooms *config.CherryRooms, roomName string, mSet, sSet []string) {
	data, _ := ioutil.ReadFile(sSet[1][1 : len(sSet[1])-1])
	cherryRooms.AddAction(roomName, mSet[0], mSet[1][1:len(mSet[1])-1], string(data))
}
Exemplo n.º 24
0
// GetFindHandle implements the handle for the find document (GET).
func GetFindHandle(newConn net.Conn, roomName, httpPayload string, rooms *config.CherryRooms, preprocessor *html.Preprocessor) {
	var replyBuffer []byte
	replyBuffer = rawhttp.MakeReplyBuffer(preprocessor.ExpandData(roomName, rooms.GetFindBotTemplate(roomName)), 200, true)
	newConn.Write(replyBuffer)
	newConn.Close()
}
Exemplo n.º 25
0
func roomImageSetter(cherryRooms *config.CherryRooms, roomName string, mSet, sSet []string) {
	//  WARN(Santiago): by now we will pass the image template as empty.
	cherryRooms.AddImage(roomName, mSet[0], mSet[1][1:len(mSet[1])-1], "", sSet[1][1:len(sSet[1])-1])
}
Exemplo n.º 26
0
// ParseCherryFile parses a file at @filepath and returns a *config.CherryRooms or a *CherryFileError.
func ParseCherryFile(filepath string) (*config.CherryRooms, *CherryFileError) {
	var cherryRooms *config.CherryRooms
	var cherryFileData []byte
	var data string
	var err *CherryFileError
	var line int
	cherryFileData, ioErr := ioutil.ReadFile(filepath)
	if ioErr != nil {
		return nil, NewCherryFileError("(no file)", -1, fmt.Sprintf("unable to read from \"%s\" [more details: %s].", filepath, ioErr.Error()))
	}
	data, _, line, err = GetDataFromSection("cherry.root", string(cherryFileData), 1, filepath)
	if err != nil {
		return nil, err
	}
	var set []string
	cherryRooms = config.NewCherryRooms()
	set, line, data = GetNextSetFromData(data, line, "=")
	for len(set) == 2 {
		switch set[0] {
		case "servername", "certificate", "private-key":
			if set[1][0] != '"' || set[1][len(set[1])-1] != '"' {
				return nil, NewCherryFileError(filepath, line, fmt.Sprintf("invalid string."))
			}
			data := set[1][1 : len(set[1])-1]

			if set[0] == "certificate" || set[0] == "private-key" {
				if _, err := os.Stat(data); os.IsNotExist(err) {
					return nil, NewCherryFileError(filepath, line, fmt.Sprintf("\"%s\" must receive an accessible file path.", set[0]))
				}
			}

			if set[0] == "servername" {
				cherryRooms.SetServername(data)
			} else if set[0] == "certificate" {
				cherryRooms.SetCertificatePath(data)
			} else if set[0] == "private-key" {
				cherryRooms.SetPrivateKeyPath(data)
			}
			break

		default:
			return nil, NewCherryFileError(filepath, line, fmt.Sprintf("unknown config set \"%s\".", set[0]))
		}
		set, line, data = GetNextSetFromData(data, line, "=")
	}
	if cherryRooms.GetServername() == "localhost" {
		fmt.Println("WARN: cherry.root.servername is equals to \"localhost\". Things will not work outside this node.")
	}
	data, _, line, err = GetDataFromSection("cherry.rooms", string(cherryFileData), 1, filepath)
	if err != nil {
		return nil, err
	}
	//  INFO(Santiago): Adding all scanned rooms from the first cherry.rooms section found
	//                  [cherry branches were scanned too at this point].
	set, line, data = GetNextSetFromData(data, line, ":")
	for len(set) == 2 {
		if cherryRooms.HasRoom(set[0]) {
			return nil, NewCherryFileError(filepath, line, fmt.Sprintf("room \"%s\" redeclared.", set[0]))
		}
		var value int64
		var convErr error
		value, convErr = strconv.ParseInt(set[1], 10, 16)
		if convErr != nil {
			return nil, NewCherryFileError(filepath, line, fmt.Sprintf("invalid port value \"%s\" [more details: %s].", set[1], convErr))
		}
		var port int16
		port = int16(value)
		if cherryRooms.PortBusyByAnotherRoom(port) {
			return nil, NewCherryFileError(filepath, line, fmt.Sprintf("the port \"%s\" is already busy by another room.", set[1]))
		}

		cherryRooms.AddRoom(set[0], port)

		errRoomConfig := GetRoomTemplates(set[0], cherryRooms, string(cherryFileData), filepath)
		if errRoomConfig != nil {
			return nil, errRoomConfig
		}

		errRoomConfig = GetRoomActions(set[0], cherryRooms, string(cherryFileData), filepath)
		if errRoomConfig != nil {
			return nil, errRoomConfig
		}

		//  INFO(Santiago): until now these two following sections are non-mandatory.

		_ = GetRoomImages(set[0], cherryRooms, string(cherryFileData), filepath)

		//_ = GetRoomSounds(set[0], cherryRooms, string(cherryFileData), filepath)

		errRoomConfig = GetRoomMisc(set[0], cherryRooms, string(cherryFileData), filepath)
		if errRoomConfig != nil {
			return nil, errRoomConfig
		}

		//  INFO(Santiago): Let's transfer the next room from file to the memory.
		set, line, data = GetNextSetFromData(data, line, ":")
	}
	return cherryRooms, nil
}