1package filexfer
2
3import (
4	"fmt"
5)
6
7// StatusPacket defines the SSH_FXP_STATUS packet.
8//
9// Specified in https://tools.ietf.org/html/draft-ietf-secsh-filexfer-02#section-7
10type StatusPacket struct {
11	StatusCode   Status
12	ErrorMessage string
13	LanguageTag  string
14}
15
16// Error makes StatusPacket an error type.
17func (p *StatusPacket) Error() string {
18	if p.ErrorMessage == "" {
19		return "sftp: " + p.StatusCode.String()
20	}
21
22	return fmt.Sprintf("sftp: %q (%s)", p.ErrorMessage, p.StatusCode)
23}
24
25// Is returns true if target is a StatusPacket with the same StatusCode,
26// or target is a Status code which is the same as SatusCode.
27func (p *StatusPacket) Is(target error) bool {
28	if target, ok := target.(*StatusPacket); ok {
29		return p.StatusCode == target.StatusCode
30	}
31
32	return p.StatusCode == target
33}
34
35// Type returns the SSH_FXP_xy value associated with this packet type.
36func (p *StatusPacket) Type() PacketType {
37	return PacketTypeStatus
38}
39
40// MarshalPacket returns p as a two-part binary encoding of p.
41func (p *StatusPacket) MarshalPacket(reqid uint32, b []byte) (header, payload []byte, err error) {
42	buf := NewBuffer(b)
43	if buf.Cap() < 9 {
44		// uint32(error/status code) + string(error message) + string(language tag)
45		size := 4 + 4 + len(p.ErrorMessage) + 4 + len(p.LanguageTag)
46		buf = NewMarshalBuffer(size)
47	}
48
49	buf.StartPacket(PacketTypeStatus, reqid)
50	buf.AppendUint32(uint32(p.StatusCode))
51	buf.AppendString(p.ErrorMessage)
52	buf.AppendString(p.LanguageTag)
53
54	return buf.Packet(payload)
55}
56
57// UnmarshalPacketBody unmarshals the packet body from the given Buffer.
58// It is assumed that the uint32(request-id) has already been consumed.
59func (p *StatusPacket) UnmarshalPacketBody(buf *Buffer) (err error) {
60	statusCode, err := buf.ConsumeUint32()
61	if err != nil {
62		return err
63	}
64	p.StatusCode = Status(statusCode)
65
66	if p.ErrorMessage, err = buf.ConsumeString(); err != nil {
67		return err
68	}
69
70	if p.LanguageTag, err = buf.ConsumeString(); err != nil {
71		return err
72	}
73
74	return nil
75}
76
77// HandlePacket defines the SSH_FXP_HANDLE packet.
78type HandlePacket struct {
79	Handle string
80}
81
82// Type returns the SSH_FXP_xy value associated with this packet type.
83func (p *HandlePacket) Type() PacketType {
84	return PacketTypeHandle
85}
86
87// MarshalPacket returns p as a two-part binary encoding of p.
88func (p *HandlePacket) MarshalPacket(reqid uint32, b []byte) (header, payload []byte, err error) {
89	buf := NewBuffer(b)
90	if buf.Cap() < 9 {
91		size := 4 + len(p.Handle) // string(handle)
92		buf = NewMarshalBuffer(size)
93	}
94
95	buf.StartPacket(PacketTypeHandle, reqid)
96	buf.AppendString(p.Handle)
97
98	return buf.Packet(payload)
99}
100
101// UnmarshalPacketBody unmarshals the packet body from the given Buffer.
102// It is assumed that the uint32(request-id) has already been consumed.
103func (p *HandlePacket) UnmarshalPacketBody(buf *Buffer) (err error) {
104	if p.Handle, err = buf.ConsumeString(); err != nil {
105		return err
106	}
107
108	return nil
109}
110
111// DataPacket defines the SSH_FXP_DATA packet.
112type DataPacket struct {
113	Data []byte
114}
115
116// Type returns the SSH_FXP_xy value associated with this packet type.
117func (p *DataPacket) Type() PacketType {
118	return PacketTypeData
119}
120
121// MarshalPacket returns p as a two-part binary encoding of p.
122func (p *DataPacket) MarshalPacket(reqid uint32, b []byte) (header, payload []byte, err error) {
123	buf := NewBuffer(b)
124	if buf.Cap() < 9 {
125		size := 4 // uint32(len(data)); data content in payload
126		buf = NewMarshalBuffer(size)
127	}
128
129	buf.StartPacket(PacketTypeData, reqid)
130	buf.AppendUint32(uint32(len(p.Data)))
131
132	return buf.Packet(p.Data)
133}
134
135// UnmarshalPacketBody unmarshals the packet body from the given Buffer.
136// It is assumed that the uint32(request-id) has already been consumed.
137//
138// If p.Data is already populated, and of sufficient length to hold the data,
139// then this will copy the data into that byte slice.
140//
141// If p.Data has a length insufficient to hold the data,
142// then this will make a new slice of sufficient length, and copy the data into that.
143//
144// This means this _does not_ alias any of the data buffer that is passed in.
145func (p *DataPacket) UnmarshalPacketBody(buf *Buffer) (err error) {
146	data, err := buf.ConsumeByteSlice()
147	if err != nil {
148		return err
149	}
150
151	if len(p.Data) < len(data) {
152		p.Data = make([]byte, len(data))
153	}
154
155	n := copy(p.Data, data)
156	p.Data = p.Data[:n]
157	return nil
158}
159
160// NamePacket defines the SSH_FXP_NAME packet.
161type NamePacket struct {
162	Entries []*NameEntry
163}
164
165// Type returns the SSH_FXP_xy value associated with this packet type.
166func (p *NamePacket) Type() PacketType {
167	return PacketTypeName
168}
169
170// MarshalPacket returns p as a two-part binary encoding of p.
171func (p *NamePacket) MarshalPacket(reqid uint32, b []byte) (header, payload []byte, err error) {
172	buf := NewBuffer(b)
173	if buf.Cap() < 9 {
174		size := 4 // uint32(len(entries))
175
176		for _, e := range p.Entries {
177			size += e.Len()
178		}
179
180		buf = NewMarshalBuffer(size)
181	}
182
183	buf.StartPacket(PacketTypeName, reqid)
184	buf.AppendUint32(uint32(len(p.Entries)))
185
186	for _, e := range p.Entries {
187		e.MarshalInto(buf)
188	}
189
190	return buf.Packet(payload)
191}
192
193// UnmarshalPacketBody unmarshals the packet body from the given Buffer.
194// It is assumed that the uint32(request-id) has already been consumed.
195func (p *NamePacket) UnmarshalPacketBody(buf *Buffer) (err error) {
196	count, err := buf.ConsumeUint32()
197	if err != nil {
198		return err
199	}
200
201	p.Entries = make([]*NameEntry, 0, count)
202
203	for i := uint32(0); i < count; i++ {
204		var e NameEntry
205		if err := e.UnmarshalFrom(buf); err != nil {
206			return err
207		}
208
209		p.Entries = append(p.Entries, &e)
210	}
211
212	return nil
213}
214
215// AttrsPacket defines the SSH_FXP_ATTRS packet.
216type AttrsPacket struct {
217	Attrs Attributes
218}
219
220// Type returns the SSH_FXP_xy value associated with this packet type.
221func (p *AttrsPacket) Type() PacketType {
222	return PacketTypeAttrs
223}
224
225// MarshalPacket returns p as a two-part binary encoding of p.
226func (p *AttrsPacket) MarshalPacket(reqid uint32, b []byte) (header, payload []byte, err error) {
227	buf := NewBuffer(b)
228	if buf.Cap() < 9 {
229		size := p.Attrs.Len() // ATTRS(attrs)
230		buf = NewMarshalBuffer(size)
231	}
232
233	buf.StartPacket(PacketTypeAttrs, reqid)
234	p.Attrs.MarshalInto(buf)
235
236	return buf.Packet(payload)
237}
238
239// UnmarshalPacketBody unmarshals the packet body from the given Buffer.
240// It is assumed that the uint32(request-id) has already been consumed.
241func (p *AttrsPacket) UnmarshalPacketBody(buf *Buffer) (err error) {
242	return p.Attrs.UnmarshalFrom(buf)
243}
244