X-Git-Url: https://git.r.bdr.sh/rbdr/mobius/blobdiff_plain/9cf66aeafbcbb9237fedc2efc97cc2856eb60f7f..f6f1d88969e12eadb7013397cdad3c4c5625988c:/hotline/transaction_handlers.go?ds=sidebyside diff --git a/hotline/transaction_handlers.go b/hotline/transaction_handlers.go index 4bb956a..1a079f7 100644 --- a/hotline/transaction_handlers.go +++ b/hotline/transaction_handlers.go @@ -1,6 +1,7 @@ package hotline import ( + "bufio" "bytes" "encoding/binary" "errors" @@ -414,11 +415,11 @@ func HandleGetFileInfo(cc *ClientConn, t *Transaction) (res []Transaction, err e return res, err } -// HandleSetFileInfo updates a file or folder name and/or comment from the Get Info window +// HandleSetFileInfo updates a file or folder Name and/or comment from the Get Info window // Fields used in the request: -// * 201 File name +// * 201 File Name // * 202 File path Optional -// * 211 File new name Optional +// * 211 File new Name Optional // * 210 File comment Optional // Fields used in the reply: None func HandleSetFileInfo(cc *ClientConn, t *Transaction) (res []Transaction, err error) { @@ -516,7 +517,7 @@ func HandleSetFileInfo(cc *ClientConn, t *Transaction) (res []Transaction, err e // HandleDeleteFile deletes a file or folder // Fields used in the request: -// * 201 File name +// * 201 File Name // * 202 File path // Fields used in the reply: none func HandleDeleteFile(cc *ClientConn, t *Transaction) (res []Transaction, err error) { @@ -574,7 +575,7 @@ func HandleMoveFile(cc *ClientConn, t *Transaction) (res []Transaction, err erro return res, err } - cc.logger.Infow("Move file", "src", filePath+"/"+fileName, "dst", fileNewPath+"/"+fileName) + cc.logger.Info("Move file", "src", filePath+"/"+fileName, "dst", fileNewPath+"/"+fileName) hlFile, err := newFileWrapper(cc.Server.FS, filePath, 0) if err != nil { @@ -636,10 +637,10 @@ func HandleNewFolder(cc *ClientConn, t *Transaction) (res []Transaction, err err return res, fmt.Errorf("invalid filepath encoding: %w", err) } - // TODO: check path and folder name lengths + // TODO: check path and folder Name lengths if _, err := cc.Server.FS.Stat(newFolderPath); !os.IsNotExist(err) { - msg := fmt.Sprintf("Cannot create folder \"%s\" because there is already a file or folder with that name.", folderName) + msg := fmt.Sprintf("Cannot create folder \"%s\" because there is already a file or folder with that Name.", folderName) return []Transaction{cc.NewErrReply(t, msg)}, nil } @@ -658,7 +659,7 @@ func HandleSetUser(cc *ClientConn, t *Transaction) (res []Transaction, err error return res, err } - login := decodeString(t.GetField(FieldUserLogin).Data) + login := string(encodeString(t.GetField(FieldUserLogin).Data)) userName := string(t.GetField(FieldUserName).Data) newAccessLvl := t.GetField(FieldUserAccess).Data @@ -771,9 +772,21 @@ func HandleListUsers(cc *ClientConn, t *Transaction) (res []Transaction, err err // performed. This seems to be the only place in the Hotline protocol where a data field contains another data field. func HandleUpdateUser(cc *ClientConn, t *Transaction) (res []Transaction, err error) { for _, field := range t.Fields { - subFields, err := ReadFields(field.Data[0:2], field.Data[2:]) - if err != nil { - return res, err + + var subFields []Field + + // Create a new scanner for parsing incoming bytes into transaction tokens + scanner := bufio.NewScanner(bytes.NewReader(field.Data[2:])) + scanner.Split(fieldScanner) + + for i := 0; i < int(binary.BigEndian.Uint16(field.Data[0:2])); i++ { + scanner.Scan() + + var field Field + if _, err := field.Write(scanner.Bytes()); err != nil { + return res, fmt.Errorf("error reading field: %w", err) + } + subFields = append(subFields, field) } // If there's only one subfield, that indicates this is a delete operation for the login in FieldData @@ -783,8 +796,8 @@ func HandleUpdateUser(cc *ClientConn, t *Transaction) (res []Transaction, err er return res, err } - login := decodeString(getField(FieldData, &subFields).Data) - cc.logger.Infow("DeleteUser", "login", login) + login := string(encodeString(getField(FieldData, &subFields).Data)) + cc.logger.Info("DeleteUser", "login", login) if err := cc.Server.DeleteUser(login); err != nil { return res, err @@ -798,9 +811,9 @@ func HandleUpdateUser(cc *ClientConn, t *Transaction) (res []Transaction, err er // If FieldData is included, this is a rename operation where FieldData contains the login of the existing // account and FieldUserLogin contains the new login. if getField(FieldData, &subFields) != nil { - loginToRename = decodeString(getField(FieldData, &subFields).Data) + loginToRename = string(encodeString(getField(FieldData, &subFields).Data)) } - userLogin := decodeString(getField(FieldUserLogin, &subFields).Data) + userLogin := string(encodeString(getField(FieldUserLogin, &subFields).Data)) if loginToRename != "" { accountToUpdate = loginToRename } else { @@ -810,9 +823,9 @@ func HandleUpdateUser(cc *ClientConn, t *Transaction) (res []Transaction, err er // Check if accountToUpdate has an existing account. If so, we know we are updating an existing user. if acc, ok := cc.Server.Accounts[accountToUpdate]; ok { if loginToRename != "" { - cc.logger.Infow("RenameUser", "prevLogin", accountToUpdate, "newLogin", userLogin) + cc.logger.Info("RenameUser", "prevLogin", accountToUpdate, "newLogin", userLogin) } else { - cc.logger.Infow("UpdateUser", "login", accountToUpdate) + cc.logger.Info("UpdateUser", "login", accountToUpdate) } // account exists, so this is an update action @@ -842,8 +855,8 @@ func HandleUpdateUser(cc *ClientConn, t *Transaction) (res []Transaction, err er } err = cc.Server.UpdateUser( - decodeString(getField(FieldData, &subFields).Data), - decodeString(getField(FieldUserLogin, &subFields).Data), + string(encodeString(getField(FieldData, &subFields).Data)), + string(encodeString(getField(FieldUserLogin, &subFields).Data)), string(getField(FieldUserName, &subFields).Data), acc.Password, acc.Access, @@ -857,7 +870,7 @@ func HandleUpdateUser(cc *ClientConn, t *Transaction) (res []Transaction, err er return res, nil } - cc.logger.Infow("CreateUser", "login", userLogin) + cc.logger.Info("CreateUser", "login", userLogin) newAccess := accessBitmap{} copy(newAccess[:], getField(FieldUserAccess, &subFields).Data) @@ -889,7 +902,7 @@ func HandleNewUser(cc *ClientConn, t *Transaction) (res []Transaction, err error return res, err } - login := decodeString(t.GetField(FieldUserLogin).Data) + login := string(encodeString(t.GetField(FieldUserLogin).Data)) // If the account already dataFile, reply with an error if _, ok := cc.Server.Accounts[login]; ok { @@ -925,7 +938,7 @@ func HandleDeleteUser(cc *ClientConn, t *Transaction) (res []Transaction, err er return res, nil } - login := decodeString(t.GetField(FieldUserLogin).Data) + login := string(encodeString(t.GetField(FieldUserLogin).Data)) if err := cc.Server.DeleteUser(login); err != nil { return res, err @@ -958,7 +971,7 @@ func HandleUserBroadcast(cc *ClientConn, t *Transaction) (res []Transaction, err // 103 User ID // // Fields used in the reply: -// 102 User name +// 102 User Name // 101 Data User info text string func HandleGetClientInfoText(cc *ClientConn, t *Transaction) (res []Transaction, err error) { if !cc.Authorize(accessGetClientInfo) { @@ -997,8 +1010,8 @@ func HandleTranAgreed(cc *ClientConn, t *Transaction) (res []Transaction, err er cc.Icon = t.GetField(FieldUserIconID).Data - cc.logger = cc.logger.With("name", string(cc.UserName)) - cc.logger.Infow("Login successful", "clientVersion", fmt.Sprintf("%v", func() int { i, _ := byteToInt(cc.Version); return i }())) + cc.logger = cc.logger.With("Name", string(cc.UserName)) + cc.logger.Info("Login successful", "clientVersion", fmt.Sprintf("%v", func() int { i, _ := byteToInt(cc.Version); return i }())) options := t.GetField(FieldOptions).Data optBitmap := big.NewInt(int64(binary.BigEndian.Uint16(options))) @@ -1006,19 +1019,19 @@ func HandleTranAgreed(cc *ClientConn, t *Transaction) (res []Transaction, err er flagBitmap := big.NewInt(int64(binary.BigEndian.Uint16(cc.Flags))) // Check refuse private PM option - if optBitmap.Bit(refusePM) == 1 { + if optBitmap.Bit(UserOptRefusePM) == 1 { flagBitmap.SetBit(flagBitmap, UserFlagRefusePM, 1) binary.BigEndian.PutUint16(cc.Flags, uint16(flagBitmap.Int64())) } // Check refuse private chat option - if optBitmap.Bit(refuseChat) == 1 { + if optBitmap.Bit(UserOptRefuseChat) == 1 { flagBitmap.SetBit(flagBitmap, UserFlagRefusePChat, 1) binary.BigEndian.PutUint16(cc.Flags, uint16(flagBitmap.Int64())) } // Check auto response - if optBitmap.Bit(autoResponse) == 1 { + if optBitmap.Bit(UserOptAutoResponse) == 1 { cc.AutoReply = t.GetField(FieldAutomaticResponse).Data } else { cc.AutoReply = []byte{} @@ -1107,7 +1120,7 @@ func HandleDisconnectUser(cc *ClientConn, t *Transaction) (res []Transaction, er switch t.GetField(FieldOptions).Data[1] { case 1: // send message: "You are temporarily banned on this server" - cc.logger.Infow("Disconnect & temporarily ban " + string(clientConn.UserName)) + cc.logger.Info("Disconnect & temporarily ban " + string(clientConn.UserName)) res = append(res, *NewTransaction( TranServerMsg, @@ -1120,7 +1133,7 @@ func HandleDisconnectUser(cc *ClientConn, t *Transaction) (res []Transaction, er cc.Server.banList[strings.Split(clientConn.RemoteAddr, ":")[0]] = &banUntil case 2: // send message: "You are permanently banned on this server" - cc.logger.Infow("Disconnect & ban " + string(clientConn.UserName)) + cc.logger.Info("Disconnect & ban " + string(clientConn.UserName)) res = append(res, *NewTransaction( TranServerMsg, @@ -1207,7 +1220,7 @@ func HandleNewNewsCat(cc *ClientConn, t *Transaction) (res []Transaction, err er } // Fields used in the request: -// 322 News category name +// 322 News category Name // 325 News path func HandleNewNewsFldr(cc *ClientConn, t *Transaction) (res []Transaction, err error) { if !cc.Authorize(accessNewsCreateFldr) { @@ -1218,8 +1231,6 @@ func HandleNewNewsFldr(cc *ClientConn, t *Transaction) (res []Transaction, err e name := string(t.GetField(FieldFileName).Data) pathStrs := ReadNewsPath(t.GetField(FieldNewsPath).Data) - cc.logger.Infof("Creating new news folder %s", name) - cats := cc.Server.GetNewsCatByPath(pathStrs) cats[name] = NewsCategoryListData15{ Name: name, @@ -1315,11 +1326,11 @@ func HandleGetNewsArtData(cc *ClientConn, t *Transaction) (res []Transaction, er res = append(res, cc.NewReply(t, NewField(FieldNewsArtTitle, []byte(art.Title)), NewField(FieldNewsArtPoster, []byte(art.Poster)), - NewField(FieldNewsArtDate, art.Date), - NewField(FieldNewsArtPrevArt, art.PrevArt), - NewField(FieldNewsArtNextArt, art.NextArt), - NewField(FieldNewsArtParentArt, art.ParentArt), - NewField(FieldNewsArt1stChildArt, art.FirstChildArt), + NewField(FieldNewsArtDate, art.Date[:]), + NewField(FieldNewsArtPrevArt, art.PrevArt[:]), + NewField(FieldNewsArtNextArt, art.NextArt[:]), + NewField(FieldNewsArtParentArt, art.ParentArt[:]), + NewField(FieldNewsArt1stChildArt, art.FirstChildArt[:]), NewField(FieldNewsArtDataFlav, []byte("text/plain")), NewField(FieldNewsArtData, []byte(art.Data)), )) @@ -1425,10 +1436,10 @@ func HandlePostNewsArt(cc *ClientConn, t *Transaction) (res []Transaction, err e Title: string(t.GetField(FieldNewsArtTitle).Data), Poster: string(cc.UserName), Date: toHotlineTime(time.Now()), - PrevArt: []byte{0, 0, 0, 0}, - NextArt: []byte{0, 0, 0, 0}, - ParentArt: bs, - FirstChildArt: []byte{0, 0, 0, 0}, + PrevArt: [4]byte{}, + NextArt: [4]byte{}, + ParentArt: [4]byte(bs), + FirstChildArt: [4]byte{}, DataFlav: []byte("text/plain"), Data: string(t.GetField(FieldNewsArtData).Data), } @@ -1444,10 +1455,10 @@ func HandlePostNewsArt(cc *ClientConn, t *Transaction) (res []Transaction, err e prevID := uint32(keys[len(keys)-1]) nextID = prevID + 1 - binary.BigEndian.PutUint32(newArt.PrevArt, prevID) + binary.BigEndian.PutUint32(newArt.PrevArt[:], prevID) // Set next article ID - binary.BigEndian.PutUint32(cat.Articles[prevID].NextArt, nextID) + binary.BigEndian.PutUint32(cat.Articles[prevID].NextArt[:], nextID) } // Update parent article with first child reply @@ -1455,8 +1466,8 @@ func HandlePostNewsArt(cc *ClientConn, t *Transaction) (res []Transaction, err e if parentID != 0 { parentArt := cat.Articles[parentID] - if bytes.Equal(parentArt.FirstChildArt, []byte{0, 0, 0, 0}) { - binary.BigEndian.PutUint32(parentArt.FirstChildArt, nextID) + if parentArt.FirstChildArt == [4]byte{0, 0, 0, 0} { + binary.BigEndian.PutUint32(parentArt.FirstChildArt[:], nextID) } } @@ -1584,7 +1595,7 @@ func HandleDownloadFolder(cc *ClientConn, t *Transaction) (res []Transaction, er // Upload all files from the local folder and its subfolders to the specified path on the server // Fields used in the request -// 201 File name +// 201 File Name // 202 File path // 108 transfer size Total size of all items in the folder // 220 Folder item count @@ -1619,7 +1630,7 @@ func HandleUploadFolder(cc *ClientConn, t *Transaction) (res []Transaction, err // HandleUploadFile // Fields used in the request: -// 201 File name +// 201 File Name // 202 File path // 204 File transfer options "Optional // Used only to resume download, currently has value 2" @@ -1655,7 +1666,7 @@ func HandleUploadFile(cc *ClientConn, t *Transaction) (res []Transaction, err er } if _, err := cc.Server.FS.Stat(fullFilePath); err == nil { - res = append(res, cc.NewErrReply(t, fmt.Sprintf("Cannot accept upload because there is already a file named \"%v\". Try choosing a different name.", string(fileName)))) + res = append(res, cc.NewErrReply(t, fmt.Sprintf("Cannot accept upload because there is already a file named \"%v\". Try choosing a different Name.", string(fileName)))) return res, err } @@ -1704,14 +1715,14 @@ func HandleSetClientUserInfo(cc *ClientConn, t *Transaction) (res []Transaction, optBitmap := big.NewInt(int64(binary.BigEndian.Uint16(options))) flagBitmap := big.NewInt(int64(binary.BigEndian.Uint16(cc.Flags))) - flagBitmap.SetBit(flagBitmap, UserFlagRefusePM, optBitmap.Bit(refusePM)) + flagBitmap.SetBit(flagBitmap, UserFlagRefusePM, optBitmap.Bit(UserOptRefusePM)) binary.BigEndian.PutUint16(cc.Flags, uint16(flagBitmap.Int64())) - flagBitmap.SetBit(flagBitmap, UserFlagRefusePChat, optBitmap.Bit(refuseChat)) + flagBitmap.SetBit(flagBitmap, UserFlagRefusePChat, optBitmap.Bit(UserOptRefuseChat)) binary.BigEndian.PutUint16(cc.Flags, uint16(flagBitmap.Int64())) // Check auto response - if optBitmap.Bit(autoResponse) == 1 { + if optBitmap.Bit(UserOptAutoResponse) == 1 { cc.AutoReply = t.GetField(FieldAutomaticResponse).Data } else { cc.AutoReply = []byte{} @@ -1896,7 +1907,7 @@ func HandleRejectChatInvite(cc *ClientConn, t *Transaction) (res []Transaction, // HandleJoinChat is sent from a v1.8+ Hotline client when the joins a private chat // Fields used in the reply: // * 115 Chat subject -// * 300 User name with info (Optional) +// * 300 User Name with info (Optional) // * 300 (more user names with info) func HandleJoinChat(cc *ClientConn, t *Transaction) (res []Transaction, err error) { chatID := t.GetField(FieldChatID).Data @@ -1999,7 +2010,7 @@ func HandleSetChatSubject(cc *ClientConn, t *Transaction) (res []Transaction, er // HandleMakeAlias makes a file alias using the specified path. // Fields used in the request: -// 201 File name +// 201 File Name // 202 File path // 212 File new path Destination path // @@ -2024,7 +2035,7 @@ func HandleMakeAlias(cc *ClientConn, t *Transaction) (res []Transaction, err err return res, err } - cc.logger.Debugw("Make alias", "src", fullFilePath, "dst", fullNewFilePath) + cc.logger.Debug("Make alias", "src", fullFilePath, "dst", fullNewFilePath) if err := cc.Server.FS.Symlink(fullFilePath, fullNewFilePath); err != nil { res = append(res, cc.NewErrReply(t, "Error creating alias"))