aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--hotline/client.go9
-rw-r--r--hotline/file_header.go25
-rw-r--r--hotline/file_header_test.go20
-rw-r--r--hotline/file_wrapper.go5
-rw-r--r--hotline/flattened_file_object.go140
-rw-r--r--hotline/news.go69
-rw-r--r--hotline/news_test.go9
-rw-r--r--hotline/server.go22
-rw-r--r--hotline/transaction_handlers.go24
-rw-r--r--hotline/transaction_handlers_test.go8
-rw-r--r--hotline/transfer.go3
-rw-r--r--hotline/transfer_test.go2
-rw-r--r--hotline/user.go28
-rw-r--r--hotline/user_test.go7
14 files changed, 209 insertions, 162 deletions
diff --git a/hotline/client.go b/hotline/client.go
index e056438..3969e26 100644
--- a/hotline/client.go
+++ b/hotline/client.go
@@ -405,11 +405,12 @@ func handleClientGetUserNameList(ctx context.Context, c *Client, t *Transaction)
// fields, but shxd sneaks in FieldChatSubject (115) so it's important to filter explicitly for the expected
// field type. Probably a good idea to do everywhere.
if bytes.Equal(field.ID, []byte{0x01, 0x2c}) {
- u, err := ReadUser(field.Data)
- if err != nil {
- return res, err
+ var user User
+ if _, err := user.Write(field.Data); err != nil {
+ return res, fmt.Errorf("unable to read user data: %w", err)
}
- users = append(users, *u)
+
+ users = append(users, user)
}
}
c.UserList = users
diff --git a/hotline/file_header.go b/hotline/file_header.go
index 61b8e28..71a1c00 100644
--- a/hotline/file_header.go
+++ b/hotline/file_header.go
@@ -2,35 +2,36 @@ package hotline
import (
"encoding/binary"
+ "io"
"slices"
)
type FileHeader struct {
- Size []byte // Total size of FileHeader payload
- Type []byte // 0 for file, 1 for dir
- FilePath []byte // encoded file path
+ Size [2]byte // Total size of FileHeader payload
+ Type [2]byte // 0 for file, 1 for dir
+ FilePath []byte // encoded file path
}
func NewFileHeader(fileName string, isDir bool) FileHeader {
fh := FileHeader{
- Size: make([]byte, 2),
- Type: []byte{0x00, 0x00},
+ Type: [2]byte{0x00, 0x00},
FilePath: EncodeFilePath(fileName),
}
if isDir {
- fh.Type = []byte{0x00, 0x01}
+ fh.Type = [2]byte{0x00, 0x01}
}
encodedPathLen := uint16(len(fh.FilePath) + len(fh.Type))
- binary.BigEndian.PutUint16(fh.Size, encodedPathLen)
+ binary.BigEndian.PutUint16(fh.Size[:], encodedPathLen)
return fh
}
-func (fh *FileHeader) Payload() []byte {
- return slices.Concat(
- fh.Size,
- fh.Type,
+func (fh *FileHeader) Read(p []byte) (int, error) {
+ return copy(p, slices.Concat(
+ fh.Size[:],
+ fh.Type[:],
fh.FilePath,
- )
+ ),
+ ), io.EOF
}
diff --git a/hotline/file_header_test.go b/hotline/file_header_test.go
index 99fe4e4..308d6b9 100644
--- a/hotline/file_header_test.go
+++ b/hotline/file_header_test.go
@@ -1,6 +1,7 @@
package hotline
import (
+ "io"
"reflect"
"testing"
)
@@ -22,8 +23,8 @@ func TestNewFileHeader(t *testing.T) {
isDir: false,
},
want: FileHeader{
- Size: []byte{0x00, 0x0a},
- Type: []byte{0x00, 0x00},
+ Size: [2]byte{0x00, 0x0a},
+ Type: [2]byte{0x00, 0x00},
FilePath: EncodeFilePath("foo"),
},
},
@@ -34,8 +35,8 @@ func TestNewFileHeader(t *testing.T) {
isDir: true,
},
want: FileHeader{
- Size: []byte{0x00, 0x0a},
- Type: []byte{0x00, 0x01},
+ Size: [2]byte{0x00, 0x0a},
+ Type: [2]byte{0x00, 0x01},
FilePath: EncodeFilePath("foo"),
},
},
@@ -51,8 +52,8 @@ func TestNewFileHeader(t *testing.T) {
func TestFileHeader_Payload(t *testing.T) {
type fields struct {
- Size []byte
- Type []byte
+ Size [2]byte
+ Type [2]byte
FilePath []byte
}
tests := []struct {
@@ -63,8 +64,8 @@ func TestFileHeader_Payload(t *testing.T) {
{
name: "has expected payload bytes",
fields: fields{
- Size: []byte{0x00, 0x0a},
- Type: []byte{0x00, 0x00},
+ Size: [2]byte{0x00, 0x0a},
+ Type: [2]byte{0x00, 0x00},
FilePath: EncodeFilePath("foo"),
},
want: []byte{
@@ -84,7 +85,8 @@ func TestFileHeader_Payload(t *testing.T) {
Type: tt.fields.Type,
FilePath: tt.fields.FilePath,
}
- if got := fh.Payload(); !reflect.DeepEqual(got, tt.want) {
+ got, _ := io.ReadAll(fh)
+ if !reflect.DeepEqual(got, tt.want) {
t.Errorf("Read() = %v, want %v", got, tt.want)
}
})
diff --git a/hotline/file_wrapper.go b/hotline/file_wrapper.go
index 2c796ca..ec025dc 100644
--- a/hotline/file_wrapper.go
+++ b/hotline/file_wrapper.go
@@ -1,6 +1,7 @@
package hotline
import (
+ "bytes"
"encoding/binary"
"errors"
"fmt"
@@ -243,9 +244,11 @@ func (f *fileWrapper) flattenedFileObject() (*flattenedFileObject, error) {
f.ffo.FlatFileHeader.ForkCount[1] = 3
- if err := f.ffo.FlatFileInformationFork.UnmarshalBinary(b); err != nil {
+ _, err = io.Copy(&f.ffo.FlatFileInformationFork, bytes.NewReader(b))
+ if err != nil {
return nil, err
}
+
} else {
f.ffo.FlatFileInformationFork = FlatFileInformationFork{
Platform: []byte("AMAC"), // TODO: Remove hardcode to support "AWIN" Platform (maybe?)
diff --git a/hotline/flattened_file_object.go b/hotline/flattened_file_object.go
index c57de19..dfa86e0 100644
--- a/hotline/flattened_file_object.go
+++ b/hotline/flattened_file_object.go
@@ -1,8 +1,10 @@
package hotline
import (
+ "bytes"
"encoding/binary"
"io"
+ "slices"
)
type flattenedFileObject struct {
@@ -101,7 +103,8 @@ func (ffif *FlatFileInformationFork) Size() [4]byte {
func (ffo *flattenedFileObject) TransferSize(offset int64) []byte {
// get length of the flattenedFileObject, including the info fork
- payloadSize := len(ffo.BinaryMarshal())
+ b, _ := io.ReadAll(ffo)
+ payloadSize := len(b)
// length of data fork
dataSize := binary.BigEndian.Uint32(ffo.FlatFileDataForkHeader.DataSize[:])
@@ -129,23 +132,57 @@ type FlatFileForkHeader struct {
DataSize [4]byte
}
-func (ffif *FlatFileInformationFork) MarshalBinary() []byte {
- var b []byte
- b = append(b, ffif.Platform...)
- b = append(b, ffif.TypeSignature...)
- b = append(b, ffif.CreatorSignature...)
- b = append(b, ffif.Flags...)
- b = append(b, ffif.PlatformFlags...)
- b = append(b, ffif.RSVD...)
- b = append(b, ffif.CreateDate...)
- b = append(b, ffif.ModifyDate...)
- b = append(b, ffif.NameScript...)
- b = append(b, ffif.ReadNameSize()...)
- b = append(b, ffif.Name...)
- b = append(b, ffif.CommentSize...)
- b = append(b, ffif.Comment...)
+func (ffif *FlatFileInformationFork) Read(p []byte) (int, error) {
+ return copy(p,
+ slices.Concat(
+ ffif.Platform,
+ ffif.TypeSignature,
+ ffif.CreatorSignature,
+ ffif.Flags,
+ ffif.PlatformFlags,
+ ffif.RSVD,
+ ffif.CreateDate,
+ ffif.ModifyDate,
+ ffif.NameScript,
+ ffif.ReadNameSize(),
+ ffif.Name,
+ ffif.CommentSize,
+ ffif.Comment,
+ ),
+ ), io.EOF
+}
+
+// Write implements the io.Writeer interface for FlatFileInformationFork
+func (ffif *FlatFileInformationFork) Write(p []byte) (int, error) {
+ nameSize := p[70:72]
+ bs := binary.BigEndian.Uint16(nameSize)
+ total := 72 + bs
+
+ ffif.Platform = p[0:4]
+ ffif.TypeSignature = p[4:8]
+ ffif.CreatorSignature = p[8:12]
+ ffif.Flags = p[12:16]
+ ffif.PlatformFlags = p[16:20]
+ ffif.RSVD = p[20:52]
+ ffif.CreateDate = p[52:60]
+ ffif.ModifyDate = p[60:68]
+ ffif.NameScript = p[68:70]
+ ffif.NameSize = p[70:72]
+ ffif.Name = p[72:total]
+
+ if len(p) > int(total) {
+ ffif.CommentSize = p[total : total+2]
+ commentLen := binary.BigEndian.Uint16(ffif.CommentSize)
+
+ commentStartPos := int(total) + 2
+ commentEndPos := int(total) + 2 + int(commentLen)
- return b
+ ffif.Comment = p[commentStartPos:commentEndPos]
+
+ total = uint16(commentEndPos)
+ }
+
+ return int(total), nil
}
func (ffif *FlatFileInformationFork) UnmarshalBinary(b []byte) error {
@@ -178,42 +215,40 @@ func (ffif *FlatFileInformationFork) UnmarshalBinary(b []byte) error {
return nil
}
-func (ffo *flattenedFileObject) BinaryMarshal() []byte {
- var out []byte
- out = append(out, ffo.FlatFileHeader.Format[:]...)
- out = append(out, ffo.FlatFileHeader.Version[:]...)
- out = append(out, ffo.FlatFileHeader.RSVD[:]...)
- out = append(out, ffo.FlatFileHeader.ForkCount[:]...)
-
- out = append(out, []byte("INFO")...)
- out = append(out, []byte{0, 0, 0, 0}...)
- out = append(out, make([]byte, 4)...)
- out = append(out, ffo.FlatFileInformationFork.DataSize()...)
-
- out = append(out, ffo.FlatFileInformationFork.Platform...)
- out = append(out, ffo.FlatFileInformationFork.TypeSignature...)
- out = append(out, ffo.FlatFileInformationFork.CreatorSignature...)
- out = append(out, ffo.FlatFileInformationFork.Flags...)
- out = append(out, ffo.FlatFileInformationFork.PlatformFlags...)
- out = append(out, ffo.FlatFileInformationFork.RSVD...)
- out = append(out, ffo.FlatFileInformationFork.CreateDate...)
- out = append(out, ffo.FlatFileInformationFork.ModifyDate...)
- out = append(out, ffo.FlatFileInformationFork.NameScript...)
- out = append(out, ffo.FlatFileInformationFork.ReadNameSize()...)
- out = append(out, ffo.FlatFileInformationFork.Name...)
- out = append(out, ffo.FlatFileInformationFork.CommentSize...)
- out = append(out, ffo.FlatFileInformationFork.Comment...)
-
- out = append(out, ffo.FlatFileDataForkHeader.ForkType[:]...)
- out = append(out, ffo.FlatFileDataForkHeader.CompressionType[:]...)
- out = append(out, ffo.FlatFileDataForkHeader.RSVD[:]...)
- out = append(out, ffo.FlatFileDataForkHeader.DataSize[:]...)
-
- return out
+// Read implements the io.Reader interface for flattenedFileObject
+func (ffo *flattenedFileObject) Read(p []byte) (int, error) {
+ return copy(p, slices.Concat(
+ ffo.FlatFileHeader.Format[:],
+ ffo.FlatFileHeader.Version[:],
+ ffo.FlatFileHeader.RSVD[:],
+ ffo.FlatFileHeader.ForkCount[:],
+ []byte("INFO"),
+ []byte{0, 0, 0, 0},
+ make([]byte, 4),
+ ffo.FlatFileInformationFork.DataSize(),
+ ffo.FlatFileInformationFork.Platform,
+ ffo.FlatFileInformationFork.TypeSignature,
+ ffo.FlatFileInformationFork.CreatorSignature,
+ ffo.FlatFileInformationFork.Flags,
+ ffo.FlatFileInformationFork.PlatformFlags,
+ ffo.FlatFileInformationFork.RSVD,
+ ffo.FlatFileInformationFork.CreateDate,
+ ffo.FlatFileInformationFork.ModifyDate,
+ ffo.FlatFileInformationFork.NameScript,
+ ffo.FlatFileInformationFork.ReadNameSize(),
+ ffo.FlatFileInformationFork.Name,
+ ffo.FlatFileInformationFork.CommentSize,
+ ffo.FlatFileInformationFork.Comment,
+ ffo.FlatFileDataForkHeader.ForkType[:],
+ ffo.FlatFileDataForkHeader.CompressionType[:],
+ ffo.FlatFileDataForkHeader.RSVD[:],
+ ffo.FlatFileDataForkHeader.DataSize[:],
+ ),
+ ), io.EOF
}
-func (ffo *flattenedFileObject) ReadFrom(r io.Reader) (int, error) {
- var n int
+func (ffo *flattenedFileObject) ReadFrom(r io.Reader) (int64, error) {
+ var n int64
if err := binary.Read(r, binary.BigEndian, &ffo.FlatFileHeader); err != nil {
return n, err
@@ -229,7 +264,8 @@ func (ffo *flattenedFileObject) ReadFrom(r io.Reader) (int, error) {
return n, err
}
- if err := ffo.FlatFileInformationFork.UnmarshalBinary(ffifBuf); err != nil {
+ _, err := io.Copy(&ffo.FlatFileInformationFork, bytes.NewReader(ffifBuf))
+ if err != nil {
return n, err
}
diff --git a/hotline/news.go b/hotline/news.go
index 38db97a..8e13a29 100644
--- a/hotline/news.go
+++ b/hotline/news.go
@@ -4,6 +4,8 @@ import (
"bytes"
"crypto/rand"
"encoding/binary"
+ "io"
+ "slices"
"sort"
)
@@ -20,8 +22,8 @@ type ThreadedNews struct {
}
type NewsCategoryListData15 struct {
- Type []byte `yaml:"Type"` // Size 2 ; Bundle (2) or category (3)
- Count []byte // Article or SubCategory count Size 2
+ Type [2]byte `yaml:"Type"` // Size 2 ; Bundle (2) or category (3)
+ Count []byte // Article or SubCategory count Size 2
NameSize byte
Name string `yaml:"Name"` //
Articles map[uint32]*NewsArtData `yaml:"Articles"` // Optional, if Type is Category
@@ -36,11 +38,11 @@ func (newscat *NewsCategoryListData15) GetNewsArtListData() NewsArtListData {
var newsArtsPayload []byte
for i, art := range newscat.Articles {
- ID := make([]byte, 4)
- binary.BigEndian.PutUint32(ID, i)
+ id := make([]byte, 4)
+ binary.BigEndian.PutUint32(id, i)
newArt := NewsArtList{
- ID: ID,
+ ID: id,
TimeStamp: art.Date,
ParentID: art.ParentArt,
Flags: []byte{0, 0, 0, 0},
@@ -59,7 +61,7 @@ func (newscat *NewsCategoryListData15) GetNewsArtListData() NewsArtListData {
}
nald := NewsArtListData{
- ID: []byte{0, 0, 0, 0},
+ ID: [4]byte{0, 0, 0, 0},
Count: len(newsArts),
Name: []byte{},
Description: []byte{},
@@ -90,25 +92,30 @@ func (art *NewsArtData) DataSize() []byte {
}
type NewsArtListData struct {
- ID []byte `yaml:"ID"` // Size 4
- Name []byte `yaml:"Name"`
- Description []byte `yaml:"Description"` // not used?
- NewsArtList []byte // List of articles Optional (if article count > 0)
+ ID [4]byte `yaml:"ID"` // Size 4
+ Name []byte `yaml:"Name"`
+ Description []byte `yaml:"Description"` // not used?
+ NewsArtList []byte // List of articles Optional (if article count > 0)
Count int
}
-func (nald *NewsArtListData) Payload() []byte {
+func (nald *NewsArtListData) Read(p []byte) (int, error) {
count := make([]byte, 4)
binary.BigEndian.PutUint32(count, uint32(nald.Count))
- out := append(nald.ID, count...)
- out = append(out, []byte{uint8(len(nald.Name))}...)
- out = append(out, nald.Name...)
- out = append(out, []byte{uint8(len(nald.Description))}...)
- out = append(out, nald.Description...)
- out = append(out, nald.NewsArtList...)
-
- return out
+ return copy(
+ p,
+ slices.Concat(
+ nald.ID[:],
+ count,
+ []byte{uint8(len(nald.Name))},
+ nald.Name,
+ []byte{uint8(len(nald.Description))},
+ nald.Description,
+ nald.NewsArtList,
+ ),
+ ),
+ io.EOF
}
// NewsArtList is a summarized version of a NewArtData record for display in list view
@@ -167,9 +174,9 @@ func (newscat *NewsCategoryListData15) MarshalBinary() (data []byte, err error)
count := make([]byte, 2)
binary.BigEndian.PutUint16(count, uint16(len(newscat.Articles)+len(newscat.SubCats)))
- out := append(newscat.Type, count...)
+ out := append(newscat.Type[:], count...)
- if bytes.Equal(newscat.Type, []byte{0, 3}) {
+ if bytes.Equal(newscat.Type[:], []byte{0, 3}) {
// Generate a random GUID // TODO: does this need to be random?
b := make([]byte, 16)
_, err := rand.Read(b)
@@ -188,26 +195,6 @@ func (newscat *NewsCategoryListData15) MarshalBinary() (data []byte, err error)
return out, err
}
-// ReadNewsCategoryListData parses a byte slice into a NewsCategoryListData15 struct
-// For use on the client side
-func ReadNewsCategoryListData(payload []byte) NewsCategoryListData15 {
- ncld := NewsCategoryListData15{
- Type: payload[0:2],
- Count: payload[2:4],
- }
-
- if bytes.Equal(ncld.Type, []byte{0, 3}) {
- ncld.GUID = payload[4:20]
- ncld.AddSN = payload[20:24]
- ncld.AddSN = payload[24:28]
- ncld.Name = string(payload[29:])
- } else {
- ncld.Name = string(payload[5:])
- }
-
- return ncld
-}
-
func (newscat *NewsCategoryListData15) nameLen() []byte {
return []byte{uint8(len(newscat.Name))}
}
diff --git a/hotline/news_test.go b/hotline/news_test.go
index 02e11a9..44776dd 100644
--- a/hotline/news_test.go
+++ b/hotline/news_test.go
@@ -1,14 +1,13 @@
package hotline
import (
- "bytes"
"reflect"
"testing"
)
func TestNewsCategoryListData15_MarshalBinary(t *testing.T) {
type fields struct {
- Type []byte
+ Type [2]byte
Name string
Articles map[uint32]*NewsArtData
SubCats map[string]NewsCategoryListData15
@@ -26,7 +25,7 @@ func TestNewsCategoryListData15_MarshalBinary(t *testing.T) {
{
name: "returns expected bytes when type is a bundle",
fields: fields{
- Type: []byte{0x00, 0x02},
+ Type: [2]byte{0x00, 0x02},
Articles: map[uint32]*NewsArtData{
uint32(1): {
Title: "",
@@ -47,7 +46,7 @@ func TestNewsCategoryListData15_MarshalBinary(t *testing.T) {
{
name: "returns expected bytes when type is a category",
fields: fields{
- Type: []byte{0x00, 0x03},
+ Type: [2]byte{0x00, 0x03},
Articles: map[uint32]*NewsArtData{
uint32(1): {
Title: "",
@@ -82,7 +81,7 @@ func TestNewsCategoryListData15_MarshalBinary(t *testing.T) {
GUID: tt.fields.GUID,
}
gotData, err := newscat.MarshalBinary()
- if bytes.Equal(newscat.Type, []byte{0, 3}) {
+ if newscat.Type == [2]byte{0, 3} {
// zero out the random GUID before comparison
for i := 4; i < 20; i++ {
gotData[i] = 0
diff --git a/hotline/server.go b/hotline/server.go
index 78b7a37..dde469d 100644
--- a/hotline/server.go
+++ b/hotline/server.go
@@ -494,13 +494,16 @@ func (s *Server) connectedUsers() []Field {
var connectedUsers []Field
for _, c := range sortedClients(s.Clients) {
- user := User{
+ b, err := io.ReadAll(&User{
ID: *c.ID,
Icon: c.Icon,
Flags: c.Flags,
Name: string(c.UserName),
+ })
+ if err != nil {
+ return nil
}
- connectedUsers = append(connectedUsers, NewField(FieldUsernameWithInfo, user.Payload()))
+ connectedUsers = append(connectedUsers, NewField(FieldUsernameWithInfo, b))
}
return connectedUsers
}
@@ -861,8 +864,8 @@ func (s *Server) handleFileTransfer(ctx context.Context, rwc io.ReadWriter) erro
// if file transfer options are included, that means this is a "quick preview" request from a 1.5+ client
if fileTransfer.options == nil {
- // Start by sending flat file object to client
- if _, err := rwc.Write(fw.ffo.BinaryMarshal()); err != nil {
+ _, err = io.Copy(rwc, fw.ffo)
+ if err != nil {
return err
}
}
@@ -1027,11 +1030,8 @@ func (s *Server) handleFileTransfer(ctx context.Context, rwc io.ReadWriter) erro
}
fileHeader := NewFileHeader(subPath, info.IsDir())
-
- // Send the fileWrapper header to client
- if _, err := rwc.Write(fileHeader.Payload()); err != nil {
- s.Logger.Errorf("error sending file header: %v", err)
- return err
+ if _, err := io.Copy(rwc, &fileHeader); err != nil {
+ return fmt.Errorf("error sending file header: %w", err)
}
// Read the client's Next Action request
@@ -1083,8 +1083,8 @@ func (s *Server) handleFileTransfer(ctx context.Context, rwc io.ReadWriter) erro
}
// Send ffo bytes to client
- if _, err := rwc.Write(hlFile.ffo.BinaryMarshal()); err != nil {
- s.Logger.Error(err)
+ _, err = io.Copy(rwc, hlFile.ffo)
+ if err != nil {
return err
}
diff --git a/hotline/transaction_handlers.go b/hotline/transaction_handlers.go
index 793bb74..4bb956a 100644
--- a/hotline/transaction_handlers.go
+++ b/hotline/transaction_handlers.go
@@ -460,7 +460,7 @@ func HandleSetFileInfo(cc *ClientConn, t *Transaction) (res []Transaction, err e
if err != nil {
return res, err
}
- _, err = w.Write(hlFile.ffo.FlatFileInformationFork.MarshalBinary())
+ _, err = io.Copy(w, &hlFile.ffo.FlatFileInformationFork)
if err != nil {
return res, err
}
@@ -1194,7 +1194,7 @@ func HandleNewNewsCat(cc *ClientConn, t *Transaction) (res []Transaction, err er
cats := cc.Server.GetNewsCatByPath(pathStrs)
cats[name] = NewsCategoryListData15{
Name: name,
- Type: []byte{0, 3},
+ Type: [2]byte{0, 3},
Articles: map[uint32]*NewsArtData{},
SubCats: make(map[string]NewsCategoryListData15),
}
@@ -1223,7 +1223,7 @@ func HandleNewNewsFldr(cc *ClientConn, t *Transaction) (res []Transaction, err e
cats := cc.Server.GetNewsCatByPath(pathStrs)
cats[name] = NewsCategoryListData15{
Name: name,
- Type: []byte{0, 2},
+ Type: [2]byte{0, 2},
Articles: map[uint32]*NewsArtData{},
SubCats: make(map[string]NewsCategoryListData15),
}
@@ -1258,7 +1258,12 @@ func HandleGetNewsArtNameList(cc *ClientConn, t *Transaction) (res []Transaction
nald := cat.GetNewsArtListData()
- res = append(res, cc.NewReply(t, NewField(FieldNewsArtListData, nald.Payload())))
+ b, err := io.ReadAll(&nald)
+ if err != nil {
+
+ }
+
+ res = append(res, cc.NewReply(t, NewField(FieldNewsArtListData, b)))
return res, err
}
@@ -1337,7 +1342,7 @@ func HandleDelNewsItem(cc *ClientConn, t *Transaction) (res []Transaction, err e
}
}
- if bytes.Equal(cats[delName].Type, []byte{0, 3}) {
+ if cats[delName].Type == [2]byte{0, 3} {
if !cc.Authorize(accessNewsDeleteCat) {
return append(res, cc.NewErrReply(t, "You are not allowed to delete news categories.")), nil
}
@@ -1918,14 +1923,17 @@ func HandleJoinChat(cc *ClientConn, t *Transaction) (res []Transaction, err erro
replyFields := []Field{NewField(FieldChatSubject, []byte(privChat.Subject))}
for _, c := range sortedClients(privChat.ClientConn) {
- user := User{
+
+ b, err := io.ReadAll(&User{
ID: *c.ID,
Icon: c.Icon,
Flags: c.Flags,
Name: string(c.UserName),
+ })
+ if err != nil {
+ return res, nil
}
-
- replyFields = append(replyFields, NewField(FieldUsernameWithInfo, user.Payload()))
+ replyFields = append(replyFields, NewField(FieldUsernameWithInfo, b))
}
res = append(res, cc.NewReply(t, replyFields...))
diff --git a/hotline/transaction_handlers_test.go b/hotline/transaction_handlers_test.go
index 94854f2..ca1722f 100644
--- a/hotline/transaction_handlers_test.go
+++ b/hotline/transaction_handlers_test.go
@@ -3191,7 +3191,7 @@ func TestHandleDelNewsItem(t *testing.T) {
Server: &Server{
ThreadedNews: &ThreadedNews{Categories: map[string]NewsCategoryListData15{
"test": {
- Type: []byte{0, 3},
+ Type: [2]byte{0, 3},
Count: nil,
NameSize: 0,
Name: "zz",
@@ -3237,7 +3237,7 @@ func TestHandleDelNewsItem(t *testing.T) {
Server: &Server{
ThreadedNews: &ThreadedNews{Categories: map[string]NewsCategoryListData15{
"testcat": {
- Type: []byte{0, 2},
+ Type: [2]byte{0, 2},
Count: nil,
NameSize: 0,
Name: "test",
@@ -3293,7 +3293,7 @@ func TestHandleDelNewsItem(t *testing.T) {
}(),
ThreadedNews: &ThreadedNews{Categories: map[string]NewsCategoryListData15{
"testcat": {
- Type: []byte{0, 2},
+ Type: [2]byte{0, 2},
Count: nil,
NameSize: 0,
Name: "test",
@@ -3847,7 +3847,7 @@ func TestHandleNewNewsFldr(t *testing.T) {
}(),
ThreadedNews: &ThreadedNews{Categories: map[string]NewsCategoryListData15{
"test": {
- Type: []byte{0, 2},
+ Type: [2]byte{0, 2},
Count: nil,
NameSize: 0,
Name: "test",
diff --git a/hotline/transfer.go b/hotline/transfer.go
index 2acb5d6..543b00c 100644
--- a/hotline/transfer.go
+++ b/hotline/transfer.go
@@ -37,7 +37,7 @@ func receiveFile(r io.Reader, targetFile, resForkFile, infoFork, counterWriter i
}
// Write the information fork
- _, err := infoFork.Write(ffo.FlatFileInformationFork.MarshalBinary())
+ _, err := io.Copy(infoFork, &ffo.FlatFileInformationFork)
if err != nil {
return err
}
@@ -58,6 +58,7 @@ func receiveFile(r io.Reader, targetFile, resForkFile, infoFork, counterWriter i
return nil
}
+// TODO: read the banner once on startup instead of once per banner fetch
func (s *Server) bannerDownload(w io.Writer) error {
bannerBytes, err := os.ReadFile(filepath.Join(s.ConfigDir, s.Config.BannerFile))
if err != nil {
diff --git a/hotline/transfer_test.go b/hotline/transfer_test.go
index fb3e0da..2bcee11 100644
--- a/hotline/transfer_test.go
+++ b/hotline/transfer_test.go
@@ -135,7 +135,7 @@ func Test_receiveFile(t *testing.T) {
},
}
fakeFileData := []byte{1, 2, 3}
- b := testFile.BinaryMarshal()
+ b, _ := io.ReadAll(&testFile)
b = append(b, fakeFileData...)
return bytes.NewReader(b)
}(),
diff --git a/hotline/user.go b/hotline/user.go
index 5b1b705..7d6f851 100644
--- a/hotline/user.go
+++ b/hotline/user.go
@@ -2,6 +2,8 @@ package hotline
import (
"encoding/binary"
+ "io"
+ "slices"
)
// User flags are stored as a 2 byte bitmap and represent various user states
@@ -26,7 +28,7 @@ type User struct {
Name string // Variable length user name
}
-func (u User) Payload() []byte {
+func (u *User) Read(p []byte) (int, error) {
nameLen := make([]byte, 2)
binary.BigEndian.PutUint16(nameLen, uint16(len(u.Name)))
@@ -43,17 +45,23 @@ func (u User) Payload() []byte {
out = append(out, nameLen...)
out = append(out, u.Name...)
- return out
+ return copy(p, slices.Concat(
+ u.ID,
+ u.Icon,
+ u.Flags,
+ nameLen,
+ []byte(u.Name),
+ )), io.EOF
}
-func ReadUser(b []byte) (*User, error) {
- u := &User{
- ID: b[0:2],
- Icon: b[2:4],
- Flags: b[4:6],
- Name: string(b[8:]),
- }
- return u, nil
+func (u *User) Write(p []byte) (int, error) {
+ namelen := int(binary.BigEndian.Uint16(p[6:8]))
+ u.ID = p[0:2]
+ u.Icon = p[2:4]
+ u.Flags = p[4:6]
+ u.Name = string(p[8 : 8+namelen])
+
+ return 8 + namelen, nil
}
// decodeString decodes an obfuscated user string from a client
diff --git a/hotline/user_test.go b/hotline/user_test.go
index 214b1fb..6258316 100644
--- a/hotline/user_test.go
+++ b/hotline/user_test.go
@@ -44,13 +44,14 @@ func TestReadUser(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- got, err := ReadUser(tt.args.b)
+ var user User
+ _, err := user.Write(tt.args.b)
if (err != nil) != tt.wantErr {
t.Errorf("ReadUser() error = %v, wantErr %v", err, tt.wantErr)
return
}
- if !assert.Equal(t, tt.want, got) {
- t.Errorf("ReadUser() got = %v, want %v", got, tt.want)
+ if !assert.Equal(t, tt.want, &user) {
+ t.Errorf("ReadUser() got = %v, want %v", user, tt.want)
}
})
}