"math/big"
"os"
"path"
+ "path/filepath"
"sort"
"strings"
"time"
Name: "tranUserBroadcast",
Handler: HandleUserBroadcast,
},
+ tranDownloadBanner: {
+ Name: "tranDownloadBanner",
+ Handler: HandleDownloadBanner,
+ },
}
func HandleChatSend(cc *ClientConn, t *Transaction) (res []Transaction, err error) {
formattedMsg = fmt.Sprintf("\r*** %s %s", cc.UserName, t.GetField(fieldData).Data)
}
- if bytes.Equal(t.GetField(fieldData).Data, []byte("/stats")) {
- formattedMsg = strings.Replace(cc.Server.Stats.String(), "\n", "\r", -1)
- }
-
chatID := t.GetField(fieldChatID).Data
// a non-nil chatID indicates the message belongs to a private chat
if chatID != nil {
fileName := t.GetField(fieldFileName).Data
filePath := t.GetField(fieldFilePath).Data
- ffo, err := NewFlattenedFileObject(cc.Server.Config.FileRoot, filePath, fileName, 0)
+ fullFilePath, err := readPath(cc.Server.Config.FileRoot, filePath, fileName)
+ if err != nil {
+ return res, err
+ }
+
+ fw, err := newFileWrapper(cc.Server.FS, fullFilePath, 0)
if err != nil {
return res, err
}
res = append(res, cc.NewReply(t,
- NewField(fieldFileName, fileName),
- NewField(fieldFileTypeString, ffo.FlatFileInformationFork.friendlyType()),
- NewField(fieldFileCreatorString, ffo.FlatFileInformationFork.CreatorSignature),
- NewField(fieldFileComment, ffo.FlatFileInformationFork.Comment),
- NewField(fieldFileType, ffo.FlatFileInformationFork.TypeSignature),
- NewField(fieldFileCreateDate, ffo.FlatFileInformationFork.CreateDate),
- NewField(fieldFileModifyDate, ffo.FlatFileInformationFork.ModifyDate),
- NewField(fieldFileSize, ffo.FlatFileDataForkHeader.DataSize[:]),
+ NewField(fieldFileName, []byte(fw.name)),
+ NewField(fieldFileTypeString, fw.ffo.FlatFileInformationFork.friendlyType()),
+ NewField(fieldFileCreatorString, fw.ffo.FlatFileInformationFork.friendlyCreator()),
+ NewField(fieldFileComment, fw.ffo.FlatFileInformationFork.Comment),
+ NewField(fieldFileType, fw.ffo.FlatFileInformationFork.TypeSignature),
+ NewField(fieldFileCreateDate, fw.ffo.FlatFileInformationFork.CreateDate),
+ NewField(fieldFileModifyDate, fw.ffo.FlatFileInformationFork.ModifyDate),
+ NewField(fieldFileSize, fw.totalSize()),
))
return res, err
}
// HandleSetFileInfo updates a file or folder name and/or comment from the Get Info window
-// TODO: Implement support for comments
// Fields used in the request:
// * 201 File name
// * 202 File path Optional
return res, err
}
+ fi, err := cc.Server.FS.Stat(fullFilePath)
+ if err != nil {
+ return res, err
+ }
+
+ hlFile, err := newFileWrapper(cc.Server.FS, fullFilePath, 0)
+ if err != nil {
+ return res, err
+ }
+ if t.GetField(fieldFileComment).Data != nil {
+ switch mode := fi.Mode(); {
+ case mode.IsDir():
+ if !authorize(cc.Account.Access, accessSetFolderComment) {
+ res = append(res, cc.NewErrReply(t, "You are not allowed to set comments for folders."))
+ return res, err
+ }
+ case mode.IsRegular():
+ if !authorize(cc.Account.Access, accessSetFileComment) {
+ res = append(res, cc.NewErrReply(t, "You are not allowed to set comments for files."))
+ return res, err
+ }
+ }
+
+ if err := hlFile.ffo.FlatFileInformationFork.setComment(t.GetField(fieldFileComment).Data); err != nil {
+ return res, err
+ }
+ w, err := hlFile.infoForkWriter()
+ if err != nil {
+ return res, err
+ }
+ _, err = w.Write(hlFile.ffo.FlatFileInformationFork.MarshalBinary())
+ if err != nil {
+ return res, err
+ }
+ }
+
fullNewFilePath, err := readPath(cc.Server.Config.FileRoot, filePath, t.GetField(fieldFileNewName).Data)
if err != nil {
return nil, err
}
- // fileComment := t.GetField(fieldFileComment).Data
fileNewName := t.GetField(fieldFileNewName).Data
if fileNewName != nil {
- fi, err := cc.Server.FS.Stat(fullFilePath)
- if err != nil {
- return res, err
- }
switch mode := fi.Mode(); {
case mode.IsDir():
if !authorize(cc.Account.Access, accessRenameFolder) {
res = append(res, cc.NewErrReply(t, "You are not allowed to rename folders."))
return res, err
}
+ err = os.Rename(fullFilePath, fullNewFilePath)
+ if os.IsNotExist(err) {
+ res = append(res, cc.NewErrReply(t, "Cannot rename folder "+string(fileName)+" because it does not exist or cannot be found."))
+ return res, err
+ }
case mode.IsRegular():
if !authorize(cc.Account.Access, accessRenameFile) {
res = append(res, cc.NewErrReply(t, "You are not allowed to rename files."))
return res, err
}
- }
-
- err = os.Rename(fullFilePath, fullNewFilePath)
- if os.IsNotExist(err) {
- res = append(res, cc.NewErrReply(t, "Cannot rename file "+string(fileName)+" because it does not exist or cannot be found."))
- return res, err
+ fileDir, err := readPath(cc.Server.Config.FileRoot, filePath, []byte{})
+ if err != nil {
+ return nil, err
+ }
+ hlFile.name = string(fileNewName)
+ err = hlFile.move(fileDir)
+ if os.IsNotExist(err) {
+ res = append(res, cc.NewErrReply(t, "Cannot rename file "+string(fileName)+" because it does not exist or cannot be found."))
+ return res, err
+ }
+ if err != nil {
+ panic(err)
+ }
}
}
return res, err
}
- cc.Server.Logger.Debugw("Delete file", "src", fullFilePath)
+ hlFile, err := newFileWrapper(cc.Server.FS, fullFilePath, 0)
+ if err != nil {
+ return res, err
+ }
- fi, err := os.Stat(fullFilePath)
+ fi, err := hlFile.dataFile()
if err != nil {
res = append(res, cc.NewErrReply(t, "Cannot delete file "+string(fileName)+" because it does not exist or cannot be found."))
return res, nil
}
+
switch mode := fi.Mode(); {
case mode.IsDir():
if !authorize(cc.Account.Access, accessDeleteFolder) {
}
}
- if err := os.RemoveAll(fullFilePath); err != nil {
+ if err := hlFile.delete(); err != nil {
return res, err
}
// HandleMoveFile moves files or folders. Note: seemingly not documented
func HandleMoveFile(cc *ClientConn, t *Transaction) (res []Transaction, err error) {
fileName := string(t.GetField(fieldFileName).Data)
- filePath := cc.Server.Config.FileRoot + ReadFilePath(t.GetField(fieldFilePath).Data)
- fileNewPath := cc.Server.Config.FileRoot + ReadFilePath(t.GetField(fieldFileNewPath).Data)
- cc.Server.Logger.Debugw("Move file", "src", filePath+"/"+fileName, "dst", fileNewPath+"/"+fileName)
+ filePath, err := readPath(cc.Server.Config.FileRoot, t.GetField(fieldFilePath).Data, t.GetField(fieldFileName).Data)
+ if err != nil {
+ return res, err
+ }
+
+ fileNewPath, err := readPath(cc.Server.Config.FileRoot, t.GetField(fieldFileNewPath).Data, nil)
+ if err != nil {
+ return res, err
+ }
+
+ cc.logger.Infow("Move file", "src", filePath+"/"+fileName, "dst", fileNewPath+"/"+fileName)
+
+ hlFile, err := newFileWrapper(cc.Server.FS, filePath, 0)
+ if err != nil {
+ return res, err
+ }
- fp := filePath + "/" + fileName
- fi, err := os.Stat(fp)
+ fi, err := hlFile.dataFile()
+ if err != nil {
+ res = append(res, cc.NewErrReply(t, "Cannot delete file "+fileName+" because it does not exist or cannot be found."))
+ return res, err
+ }
if err != nil {
return res, err
}
return res, err
}
}
-
- err = os.Rename(filePath+"/"+fileName, fileNewPath+"/"+fileName)
- if os.IsNotExist(err) {
- res = append(res, cc.NewErrReply(t, "Cannot delete file "+fileName+" because it does not exist or cannot be found."))
+ if err := hlFile.move(fileNewPath); err != nil {
return res, err
}
- if err != nil {
- return []Transaction{}, err
- }
- // TODO: handle other possible errors; e.g. file delete fails due to file permission issue
+ // TODO: handle other possible errors; e.g. fileWrapper delete fails due to fileWrapper permission issue
res = append(res, cc.NewReply(t))
return res, err
res = append(res, cc.NewErrReply(t, "You are not allowed to create folders."))
return res, err
}
- newFolderPath := cc.Server.Config.FileRoot
folderName := string(t.GetField(fieldFileName).Data)
folderName = path.Join("/", folderName)
+ var subPath string
+
// fieldFilePath is only present for nested paths
if t.GetField(fieldFilePath).Data != nil {
var newFp FilePath
if err != nil {
return nil, err
}
- newFolderPath += newFp.String()
+
+ for _, pathItem := range newFp.Items {
+ subPath = filepath.Join("/", subPath, string(pathItem.Name))
+ }
}
- newFolderPath = path.Join(newFolderPath, folderName)
+ newFolderPath := path.Join(cc.Server.Config.FileRoot, subPath, folderName)
// TODO: check path and folder name lengths
if len(subFields) == 1 {
login := DecodeUserString(getField(fieldData, &subFields).Data)
- cc.Server.Logger.Infow("DeleteUser", "login", login)
+ cc.logger.Infow("DeleteUser", "login", login)
if !authorize(cc.Account.Access, accessDeleteUser) {
res = append(res, cc.NewErrReply(t, "You are not allowed to delete accounts."))
login := DecodeUserString(getField(fieldUserLogin, &subFields).Data)
- // check if the login exists; if so, we know we are updating an existing user
+ // check if the login dataFile; if so, we know we are updating an existing user
if acc, ok := cc.Server.Accounts[login]; ok {
- cc.Server.Logger.Infow("UpdateUser", "login", login)
+ cc.logger.Infow("UpdateUser", "login", login)
- // account exists, so this is an update action
+ // account dataFile, so this is an update action
if !authorize(cc.Account.Access, accessModifyUser) {
res = append(res, cc.NewErrReply(t, "You are not allowed to modify accounts."))
return res, err
return res, err
}
} else {
- cc.Server.Logger.Infow("CreateUser", "login", login)
+ cc.logger.Infow("CreateUser", "login", login)
if !authorize(cc.Account.Access, accessCreateUser) {
res = append(res, cc.NewErrReply(t, "You are not allowed to create new accounts."))
login := DecodeUserString(t.GetField(fieldUserLogin).Data)
- // If the account already exists, reply with an error
+ // If the account already dataFile, reply with an error
if _, ok := cc.Server.Accounts[login]; ok {
res = append(res, cc.NewErrReply(t, "Cannot create account "+login+" because there is already an account with that login."))
return res, err
cc.UserName = t.GetField(fieldUserName).Data
*cc.Icon = t.GetField(fieldUserIconID).Data
+ cc.logger = cc.logger.With("name", string(cc.UserName))
+
options := t.GetField(fieldOptions).Data
optBitmap := big.NewInt(int64(binary.BigEndian.Uint16(options)))
cc.AutoReply = []byte{}
}
- cc.notifyOthers(
+ for _, t := range cc.notifyOthers(
*NewTransaction(
tranNotifyChangeUser, nil,
NewField(fieldUserName, cc.UserName),
NewField(fieldUserIconID, *cc.Icon),
NewField(fieldUserFlags, *cc.Flags),
),
- )
+ ) {
+ cc.Server.outbox <- t
+ }
+
+ if cc.Server.Config.BannerFile != "" {
+ cc.Server.outbox <- *NewTransaction(tranServerBanner, cc.ID, NewField(fieldBannerType, []byte("JPEG")))
+ }
res = append(res, cc.NewReply(t))
return res, err
}
- newsPath := t.GetField(fieldNewsPath).Data
- cc.Server.Logger.Infow("NewsPath: ", "np", string(newsPath))
-
pathStrs := ReadNewsPath(t.GetField(fieldNewsPath).Data)
cats := cc.Server.GetNewsCatByPath(pathStrs)
name := string(t.GetField(fieldFileName).Data)
pathStrs := ReadNewsPath(t.GetField(fieldNewsPath).Data)
- cc.Server.Logger.Infof("Creating new news folder %s", name)
+ cc.logger.Infof("Creating new news folder %s", name)
cats := cc.Server.GetNewsCatByPath(pathStrs)
cats[name] = NewsCategoryListData15{
// TODO: determine if path is a Folder (Bundle) or Category and check for permission
- cc.Server.Logger.Infof("DelNewsItem %v", pathStrs)
+ cc.logger.Infof("DelNewsItem %v", pathStrs)
cats := cc.Server.ThreadedNews.Categories
fileName := t.GetField(fieldFileName).Data
filePath := t.GetField(fieldFilePath).Data
-
resumeData := t.GetField(fieldFileResumeData).Data
var dataOffset int64
if err := frd.UnmarshalBinary(t.GetField(fieldFileResumeData).Data); err != nil {
return res, err
}
+ // TODO: handle rsrc fork offset
dataOffset = int64(binary.BigEndian.Uint32(frd.ForkInfoList[0].DataSize[:]))
}
- var fp FilePath
- err = fp.UnmarshalBinary(filePath)
+ fullFilePath, err := readPath(cc.Server.Config.FileRoot, filePath, fileName)
if err != nil {
return res, err
}
- ffo, err := NewFlattenedFileObject(cc.Server.Config.FileRoot, filePath, fileName, dataOffset)
+ hlFile, err := newFileWrapper(cc.Server.FS, fullFilePath, dataOffset)
if err != nil {
return res, err
}
Type: FileDownload,
}
+ // TODO: refactor to remove this
if resumeData != nil {
var frd FileResumeData
if err := frd.UnmarshalBinary(t.GetField(fieldFileResumeData).Data); err != nil {
ft.fileResumeData = &frd
}
- xferSize := ffo.TransferSize()
+ xferSize := hlFile.ffo.TransferSize(0)
// Optional field for when a HL v1.5+ client requests file preview
// Used only for TEXT, JPEG, GIFF, BMP or PICT files
// The value will always be 2
if t.GetField(fieldFileTransferOptions).Data != nil {
ft.options = t.GetField(fieldFileTransferOptions).Data
- xferSize = ffo.FlatFileDataForkHeader.DataSize[:]
+ xferSize = hlFile.ffo.FlatFileDataForkHeader.DataSize[:]
}
cc.Server.mux.Lock()
NewField(fieldRefNum, transactionRef),
NewField(fieldWaitingCount, []byte{0x00, 0x00}), // TODO: Implement waiting count
NewField(fieldTransferSize, xferSize),
- NewField(fieldFileSize, ffo.FlatFileDataForkHeader.DataSize[:]),
+ NewField(fieldFileSize, hlFile.ffo.FlatFileDataForkHeader.DataSize[:]),
))
return res, err
replyT := cc.NewReply(t, NewField(fieldRefNum, transactionRef))
- // client has requested to resume a partially transfered file
+ // client has requested to resume a partially transferred file
if transferOptions != nil {
fullFilePath, err := readPath(cc.Server.Config.FileRoot, filePath, fileName)
if err != nil {
// Handle special case for drop box folders
if fp.IsDropbox() && !authorize(cc.Account.Access, accessViewDropBoxes) {
- res = append(res, cc.NewReply(t))
+ res = append(res, cc.NewErrReply(t, "You are not allowed to view drop boxes."))
return res, err
}
return res, err
}
-// HandleMakeAlias makes a file alias using the specified path.
+// HandleMakeAlias makes a filer alias using the specified path.
// Fields used in the request:
// 201 File name
// 202 File path
return res, err
}
- cc.Server.Logger.Debugw("Make alias", "src", fullFilePath, "dst", fullNewFilePath)
+ cc.logger.Debugw("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"))
res = append(res, cc.NewReply(t))
return res, err
}
+
+func HandleDownloadBanner(cc *ClientConn, t *Transaction) (res []Transaction, err error) {
+ transactionRef := cc.Server.NewTransactionRef()
+ data := binary.BigEndian.Uint32(transactionRef)
+
+ ft := &FileTransfer{
+ ReferenceNumber: transactionRef,
+ Type: bannerDownload,
+ }
+
+ fi, err := cc.Server.FS.Stat(filepath.Join(cc.Server.ConfigDir, cc.Server.Config.BannerFile))
+ if err != nil {
+ return res, err
+ }
+
+ size := make([]byte, 4)
+ binary.BigEndian.PutUint32(size, uint32(fi.Size()))
+
+ cc.Server.mux.Lock()
+ defer cc.Server.mux.Unlock()
+ cc.Server.FileTransfers[data] = ft
+
+ res = append(res, cc.NewReply(t,
+ NewField(fieldRefNum, transactionRef),
+ NewField(fieldTransferSize, size),
+ ))
+
+ res = append(res, cc.NewReply(t))
+ return res, err
+}