small fixes and dependency updates

This commit is contained in:
ston1th 2023-08-25 02:06:53 +02:00
commit 96405e2521
238 changed files with 3599 additions and 3467 deletions

View file

@ -6,7 +6,6 @@ linters:
disable-all: true
enable:
- asciicheck
- deadcode
- errcheck
- forcetypeassert
- gocritic
@ -18,10 +17,8 @@ linters:
- misspell
- revive
- staticcheck
- structcheck
- typecheck
- unused
- varcheck
issues:
exclude-use-default: false

View file

@ -20,35 +20,5 @@ package logr
// used whenever the caller is not interested in the logs. Logger instances
// produced by this function always compare as equal.
func Discard() Logger {
return Logger{
level: 0,
sink: discardLogSink{},
}
}
// discardLogSink is a LogSink that discards all messages.
type discardLogSink struct{}
// Verify that it actually implements the interface
var _ LogSink = discardLogSink{}
func (l discardLogSink) Init(RuntimeInfo) {
}
func (l discardLogSink) Enabled(int) bool {
return false
}
func (l discardLogSink) Info(int, string, ...interface{}) {
}
func (l discardLogSink) Error(error, string, ...interface{}) {
}
func (l discardLogSink) WithValues(...interface{}) LogSink {
return l
}
func (l discardLogSink) WithName(string) LogSink {
return l
return New(nil)
}

View file

@ -21,7 +21,7 @@ limitations under the License.
// to back that API. Packages in the Go ecosystem can depend on this package,
// while callers can implement logging with whatever backend is appropriate.
//
// Usage
// # Usage
//
// Logging is done using a Logger instance. Logger is a concrete type with
// methods, which defers the actual logging to a LogSink interface. The main
@ -30,16 +30,20 @@ limitations under the License.
// "structured logging".
//
// With Go's standard log package, we might write:
// log.Printf("setting target value %s", targetValue)
//
// log.Printf("setting target value %s", targetValue)
//
// With logr's structured logging, we'd write:
// logger.Info("setting target", "value", targetValue)
//
// logger.Info("setting target", "value", targetValue)
//
// Errors are much the same. Instead of:
// log.Printf("failed to open the pod bay door for user %s: %v", user, err)
//
// log.Printf("failed to open the pod bay door for user %s: %v", user, err)
//
// We'd write:
// logger.Error(err, "failed to open the pod bay door", "user", user)
//
// logger.Error(err, "failed to open the pod bay door", "user", user)
//
// Info() and Error() are very similar, but they are separate methods so that
// LogSink implementations can choose to do things like attach additional
@ -47,7 +51,7 @@ limitations under the License.
// always logged, regardless of the current verbosity. If there is no error
// instance available, passing nil is valid.
//
// Verbosity
// # Verbosity
//
// Often we want to log information only when the application in "verbose
// mode". To write log lines that are more verbose, Logger has a V() method.
@ -58,20 +62,22 @@ limitations under the License.
// Error messages do not have a verbosity level and are always logged.
//
// Where we might have written:
// if flVerbose >= 2 {
// log.Printf("an unusual thing happened")
// }
//
// if flVerbose >= 2 {
// log.Printf("an unusual thing happened")
// }
//
// We can write:
// logger.V(2).Info("an unusual thing happened")
//
// Logger Names
// logger.V(2).Info("an unusual thing happened")
//
// # Logger Names
//
// Logger instances can have name strings so that all messages logged through
// that instance have additional context. For example, you might want to add
// a subsystem name:
//
// logger.WithName("compactor").Info("started", "time", time.Now())
// logger.WithName("compactor").Info("started", "time", time.Now())
//
// The WithName() method returns a new Logger, which can be passed to
// constructors or other functions for further use. Repeated use of WithName()
@ -82,25 +88,27 @@ limitations under the License.
// joining operation (e.g. whitespace, commas, periods, slashes, brackets,
// quotes, etc).
//
// Saved Values
// # Saved Values
//
// Logger instances can store any number of key/value pairs, which will be
// logged alongside all messages logged through that instance. For example,
// you might want to create a Logger instance per managed object:
//
// With the standard log package, we might write:
// log.Printf("decided to set field foo to value %q for object %s/%s",
// targetValue, object.Namespace, object.Name)
//
// log.Printf("decided to set field foo to value %q for object %s/%s",
// targetValue, object.Namespace, object.Name)
//
// With logr we'd write:
// // Elsewhere: set up the logger to log the object name.
// obj.logger = mainLogger.WithValues(
// "name", obj.name, "namespace", obj.namespace)
//
// // later on...
// obj.logger.Info("setting foo", "value", targetValue)
// // Elsewhere: set up the logger to log the object name.
// obj.logger = mainLogger.WithValues(
// "name", obj.name, "namespace", obj.namespace)
//
// Best Practices
// // later on...
// obj.logger.Info("setting foo", "value", targetValue)
//
// # Best Practices
//
// Logger has very few hard rules, with the goal that LogSink implementations
// might have a lot of freedom to differentiate. There are, however, some
@ -124,15 +132,15 @@ limitations under the License.
// around. For cases where passing a logger is optional, a pointer to Logger
// should be used.
//
// Key Naming Conventions
// # Key Naming Conventions
//
// Keys are not strictly required to conform to any specification or regex, but
// it is recommended that they:
// * be human-readable and meaningful (not auto-generated or simple ordinals)
// * be constant (not dependent on input data)
// * contain only printable characters
// * not contain whitespace or punctuation
// * use lower case for simple keys and lowerCamelCase for more complex ones
// - be human-readable and meaningful (not auto-generated or simple ordinals)
// - be constant (not dependent on input data)
// - contain only printable characters
// - not contain whitespace or punctuation
// - use lower case for simple keys and lowerCamelCase for more complex ones
//
// These guidelines help ensure that log data is processed properly regardless
// of the log implementation. For example, log implementations will try to
@ -141,51 +149,54 @@ limitations under the License.
// While users are generally free to use key names of their choice, it's
// generally best to avoid using the following keys, as they're frequently used
// by implementations:
// * "caller": the calling information (file/line) of a particular log line
// * "error": the underlying error value in the `Error` method
// * "level": the log level
// * "logger": the name of the associated logger
// * "msg": the log message
// * "stacktrace": the stack trace associated with a particular log line or
// error (often from the `Error` message)
// * "ts": the timestamp for a log line
// - "caller": the calling information (file/line) of a particular log line
// - "error": the underlying error value in the `Error` method
// - "level": the log level
// - "logger": the name of the associated logger
// - "msg": the log message
// - "stacktrace": the stack trace associated with a particular log line or
// error (often from the `Error` message)
// - "ts": the timestamp for a log line
//
// Implementations are encouraged to make use of these keys to represent the
// above concepts, when necessary (for example, in a pure-JSON output form, it
// would be necessary to represent at least message and timestamp as ordinary
// named values).
//
// Break Glass
// # Break Glass
//
// Implementations may choose to give callers access to the underlying
// logging implementation. The recommended pattern for this is:
// // Underlier exposes access to the underlying logging implementation.
// // Since callers only have a logr.Logger, they have to know which
// // implementation is in use, so this interface is less of an abstraction
// // and more of way to test type conversion.
// type Underlier interface {
// GetUnderlying() <underlying-type>
// }
//
// // Underlier exposes access to the underlying logging implementation.
// // Since callers only have a logr.Logger, they have to know which
// // implementation is in use, so this interface is less of an abstraction
// // and more of way to test type conversion.
// type Underlier interface {
// GetUnderlying() <underlying-type>
// }
//
// Logger grants access to the sink to enable type assertions like this:
// func DoSomethingWithImpl(log logr.Logger) {
// if underlier, ok := log.GetSink()(impl.Underlier) {
// implLogger := underlier.GetUnderlying()
// ...
// }
// }
//
// func DoSomethingWithImpl(log logr.Logger) {
// if underlier, ok := log.GetSink().(impl.Underlier); ok {
// implLogger := underlier.GetUnderlying()
// ...
// }
// }
//
// Custom `With*` functions can be implemented by copying the complete
// Logger struct and replacing the sink in the copy:
// // WithFooBar changes the foobar parameter in the log sink and returns a
// // new logger with that modified sink. It does nothing for loggers where
// // the sink doesn't support that parameter.
// func WithFoobar(log logr.Logger, foobar int) logr.Logger {
// if foobarLogSink, ok := log.GetSink()(FoobarSink); ok {
// log = log.WithSink(foobarLogSink.WithFooBar(foobar))
// }
// return log
// }
//
// // WithFooBar changes the foobar parameter in the log sink and returns a
// // new logger with that modified sink. It does nothing for loggers where
// // the sink doesn't support that parameter.
// func WithFoobar(log logr.Logger, foobar int) logr.Logger {
// if foobarLogSink, ok := log.GetSink().(FoobarSink); ok {
// log = log.WithSink(foobarLogSink.WithFooBar(foobar))
// }
// return log
// }
//
// Don't use New to construct a new Logger with a LogSink retrieved from an
// existing Logger. Source code attribution might not work correctly and
@ -201,11 +212,14 @@ import (
)
// New returns a new Logger instance. This is primarily used by libraries
// implementing LogSink, rather than end users.
// implementing LogSink, rather than end users. Passing a nil sink will create
// a Logger which discards all log lines.
func New(sink LogSink) Logger {
logger := Logger{}
logger.setSink(sink)
sink.Init(runtimeInfo)
if sink != nil {
sink.Init(runtimeInfo)
}
return logger
}
@ -244,7 +258,7 @@ type Logger struct {
// Enabled tests whether this Logger is enabled. For example, commandline
// flags might be used to set the logging verbosity and disable some info logs.
func (l Logger) Enabled() bool {
return l.sink.Enabled(l.level)
return l.sink != nil && l.sink.Enabled(l.level)
}
// Info logs a non-error message with the given key/value pairs as context.
@ -254,6 +268,9 @@ func (l Logger) Enabled() bool {
// information. The key/value pairs must alternate string keys and arbitrary
// values.
func (l Logger) Info(msg string, keysAndValues ...interface{}) {
if l.sink == nil {
return
}
if l.Enabled() {
if withHelper, ok := l.sink.(CallStackHelperLogSink); ok {
withHelper.GetCallStackHelper()()
@ -273,6 +290,9 @@ func (l Logger) Info(msg string, keysAndValues ...interface{}) {
// triggered this log line, if present. The err parameter is optional
// and nil may be passed instead of an error instance.
func (l Logger) Error(err error, msg string, keysAndValues ...interface{}) {
if l.sink == nil {
return
}
if withHelper, ok := l.sink.(CallStackHelperLogSink); ok {
withHelper.GetCallStackHelper()()
}
@ -284,6 +304,9 @@ func (l Logger) Error(err error, msg string, keysAndValues ...interface{}) {
// level means a log message is less important. Negative V-levels are treated
// as 0.
func (l Logger) V(level int) Logger {
if l.sink == nil {
return l
}
if level < 0 {
level = 0
}
@ -294,6 +317,9 @@ func (l Logger) V(level int) Logger {
// WithValues returns a new Logger instance with additional key/value pairs.
// See Info for documentation on how key/value pairs work.
func (l Logger) WithValues(keysAndValues ...interface{}) Logger {
if l.sink == nil {
return l
}
l.setSink(l.sink.WithValues(keysAndValues...))
return l
}
@ -304,6 +330,9 @@ func (l Logger) WithValues(keysAndValues ...interface{}) Logger {
// contain only letters, digits, and hyphens (see the package documentation for
// more information).
func (l Logger) WithName(name string) Logger {
if l.sink == nil {
return l
}
l.setSink(l.sink.WithName(name))
return l
}
@ -324,6 +353,9 @@ func (l Logger) WithName(name string) Logger {
// WithCallDepth(1) because it works with implementions that support the
// CallDepthLogSink and/or CallStackHelperLogSink interfaces.
func (l Logger) WithCallDepth(depth int) Logger {
if l.sink == nil {
return l
}
if withCallDepth, ok := l.sink.(CallDepthLogSink); ok {
l.setSink(withCallDepth.WithCallDepth(depth))
}
@ -345,6 +377,9 @@ func (l Logger) WithCallDepth(depth int) Logger {
// implementation does not support either of these, the original Logger will be
// returned.
func (l Logger) WithCallStackHelper() (func(), Logger) {
if l.sink == nil {
return func() {}, l
}
var helper func()
if withCallDepth, ok := l.sink.(CallDepthLogSink); ok {
l.setSink(withCallDepth.WithCallDepth(1))
@ -357,6 +392,11 @@ func (l Logger) WithCallStackHelper() (func(), Logger) {
return helper, l
}
// IsZero returns true if this logger is an uninitialized zero value
func (l Logger) IsZero() bool {
return l.sink == nil
}
// contextKey is how we find Loggers in a context.Context.
type contextKey struct{}
@ -442,7 +482,7 @@ type LogSink interface {
WithName(name string) LogSink
}
// CallDepthLogSink represents a Logger that knows how to climb the call stack
// CallDepthLogSink represents a LogSink that knows how to climb the call stack
// to identify the original call site and can offset the depth by a specified
// number of frames. This is useful for users who have helper functions
// between the "real" call site and the actual calls to Logger methods.
@ -467,7 +507,7 @@ type CallDepthLogSink interface {
WithCallDepth(depth int) LogSink
}
// CallStackHelperLogSink represents a Logger that knows how to climb
// CallStackHelperLogSink represents a LogSink that knows how to climb
// the call stack to identify the original call site and can skip
// intermediate helper functions if they mark themselves as
// helper. Go's testing package uses that approach.

33
vendor/github.com/pkg/sftp/attrs.go generated vendored
View file

@ -1,7 +1,7 @@
package sftp
// ssh_FXP_ATTRS support
// see http://tools.ietf.org/html/draft-ietf-secsh-filexfer-02#section-5
// see https://filezilla-project.org/specs/draft-ietf-secsh-filexfer-02.txt#section-5
import (
"os"
@ -69,6 +69,20 @@ func fileInfoFromStat(stat *FileStat, name string) os.FileInfo {
}
}
// FileInfoUidGid extends os.FileInfo and adds callbacks for Uid and Gid retrieval,
// as an alternative to *syscall.Stat_t objects on unix systems.
type FileInfoUidGid interface {
os.FileInfo
Uid() uint32
Gid() uint32
}
// FileInfoUidGid extends os.FileInfo and adds a callbacks for extended data retrieval.
type FileInfoExtendedData interface {
os.FileInfo
Extended() []StatExtended
}
func fileStatFromInfo(fi os.FileInfo) (uint32, *FileStat) {
mtime := fi.ModTime().Unix()
atime := mtime
@ -86,5 +100,22 @@ func fileStatFromInfo(fi os.FileInfo) (uint32, *FileStat) {
// os specific file stat decoding
fileStatFromInfoOs(fi, &flags, fileStat)
// The call above will include the sshFileXferAttrUIDGID in case
// the os.FileInfo can be casted to *syscall.Stat_t on unix.
// If fi implements FileInfoUidGid, retrieve Uid, Gid from it instead.
if fiExt, ok := fi.(FileInfoUidGid); ok {
flags |= sshFileXferAttrUIDGID
fileStat.UID = fiExt.Uid()
fileStat.GID = fiExt.Gid()
}
// if fi implements FileInfoExtendedData, retrieve extended data from it
if fiExt, ok := fi.(FileInfoExtendedData); ok {
fileStat.Extended = fiExt.Extended()
if len(fileStat.Extended) > 0 {
flags |= sshFileXferAttrExtended
}
}
return flags, fileStat
}

View file

@ -1,3 +1,4 @@
//go:build plan9 || windows || android
// +build plan9 windows android
package sftp

View file

@ -1,3 +1,4 @@
//go:build darwin || dragonfly || freebsd || (!android && linux) || netbsd || openbsd || solaris || aix || js
// +build darwin dragonfly freebsd !android,linux netbsd openbsd solaris aix js
package sftp

65
vendor/github.com/pkg/sftp/client.go generated vendored
View file

@ -4,6 +4,7 @@ import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"io"
"math"
"os"
@ -226,15 +227,22 @@ func NewClientPipe(rd io.Reader, wr io.WriteCloser, opts ...ClientOption) (*Clie
if err := sftp.sendInit(); err != nil {
wr.Close()
return nil, err
return nil, fmt.Errorf("error sending init packet to server: %w", err)
}
if err := sftp.recvVersion(); err != nil {
wr.Close()
return nil, err
return nil, fmt.Errorf("error receiving version packet from server: %w", err)
}
sftp.clientConn.wg.Add(1)
go sftp.loop()
go func() {
defer sftp.clientConn.wg.Done()
if err := sftp.clientConn.recv(); err != nil {
sftp.clientConn.broadcastErr(err)
}
}()
return sftp, nil
}
@ -251,11 +259,11 @@ func (c *Client) Create(path string) (*File, error) {
return c.open(path, flags(os.O_RDWR|os.O_CREATE|os.O_TRUNC))
}
const sftpProtocolVersion = 3 // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-02
const sftpProtocolVersion = 3 // https://filezilla-project.org/specs/draft-ietf-secsh-filexfer-02.txt
func (c *Client) sendInit() error {
return c.clientConn.conn.sendPacket(&sshFxInitPacket{
Version: sftpProtocolVersion, // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-02
Version: sftpProtocolVersion, // https://filezilla-project.org/specs/draft-ietf-secsh-filexfer-02.txt
})
}
@ -267,8 +275,13 @@ func (c *Client) nextID() uint32 {
func (c *Client) recvVersion() error {
typ, data, err := c.recvPacket(0)
if err != nil {
if err == io.EOF {
return fmt.Errorf("server unexpectedly closed connection: %w", io.ErrUnexpectedEOF)
}
return err
}
if typ != sshFxpVersion {
return &unexpectedPacketErr{sshFxpVersion, typ}
}
@ -277,6 +290,7 @@ func (c *Client) recvVersion() error {
if err != nil {
return err
}
if version != sftpProtocolVersion {
return &unexpectedVersionErr{sftpProtocolVersion, version}
}
@ -910,6 +924,45 @@ func (c *Client) MkdirAll(path string) error {
return nil
}
// RemoveAll delete files recursively in the directory and Recursively delete subdirectories.
// An error will be returned if no file or directory with the specified path exists
func (c *Client) RemoveAll(path string) error {
// Get the file/directory information
fi, err := c.Stat(path)
if err != nil {
return err
}
if fi.IsDir() {
// Delete files recursively in the directory
files, err := c.ReadDir(path)
if err != nil {
return err
}
for _, file := range files {
if file.IsDir() {
// Recursively delete subdirectories
err = c.RemoveAll(path + "/" + file.Name())
if err != nil {
return err
}
} else {
// Delete individual files
err = c.Remove(path + "/" + file.Name())
if err != nil {
return err
}
}
}
}
return c.Remove(path)
}
// File represents a remote file.
type File struct {
c *Client
@ -1660,7 +1713,7 @@ func (f *File) ReadFromWithConcurrency(r io.Reader, concurrency int) (read int64
Handle: f.handle,
Offset: uint64(off),
Length: uint32(n),
Data: b,
Data: b[:n],
})
select {

12
vendor/github.com/pkg/sftp/conn.go generated vendored
View file

@ -18,7 +18,9 @@ type conn struct {
}
// the orderID is used in server mode if the allocator is enabled.
// For the client mode just pass 0
// For the client mode just pass 0.
// It returns io.EOF if the connection is closed and
// there are no more packets to read.
func (c *conn) recvPacket(orderID uint32) (uint8, []byte, error) {
return recvPacket(c, c.alloc, orderID)
}
@ -61,14 +63,6 @@ func (c *clientConn) Close() error {
return c.conn.Close()
}
func (c *clientConn) loop() {
defer c.wg.Done()
err := c.recv()
if err != nil {
c.broadcastErr(err)
}
}
// recv continuously reads from the server and forwards responses to the
// appropriate channel.
func (c *clientConn) recv() error {

View file

@ -1,3 +1,4 @@
//go:build debug
// +build debug
package sftp

1
vendor/github.com/pkg/sftp/fuzz.go generated vendored
View file

@ -1,3 +1,4 @@
//go:build gofuzz
// +build gofuzz
package sftp

View file

@ -1,4 +1,4 @@
package filexfer
package sshfx
// Attributes related flags.
const (
@ -12,7 +12,7 @@ const (
// Attributes defines the file attributes type defined in draft-ietf-secsh-filexfer-02
//
// Defined in: https://tools.ietf.org/html/draft-ietf-secsh-filexfer-02#section-5
// Defined in: https://filezilla-project.org/specs/draft-ietf-secsh-filexfer-02.txt#section-5
type Attributes struct {
Flags uint32
@ -116,32 +116,32 @@ func (a *Attributes) Len() int {
}
// MarshalInto marshals e onto the end of the given Buffer.
func (a *Attributes) MarshalInto(b *Buffer) {
b.AppendUint32(a.Flags)
func (a *Attributes) MarshalInto(buf *Buffer) {
buf.AppendUint32(a.Flags)
if a.Flags&AttrSize != 0 {
b.AppendUint64(a.Size)
buf.AppendUint64(a.Size)
}
if a.Flags&AttrUIDGID != 0 {
b.AppendUint32(a.UID)
b.AppendUint32(a.GID)
buf.AppendUint32(a.UID)
buf.AppendUint32(a.GID)
}
if a.Flags&AttrPermissions != 0 {
b.AppendUint32(uint32(a.Permissions))
buf.AppendUint32(uint32(a.Permissions))
}
if a.Flags&AttrACModTime != 0 {
b.AppendUint32(a.ATime)
b.AppendUint32(a.MTime)
buf.AppendUint32(a.ATime)
buf.AppendUint32(a.MTime)
}
if a.Flags&AttrExtended != 0 {
b.AppendUint32(uint32(len(a.ExtendedAttributes)))
buf.AppendUint32(uint32(len(a.ExtendedAttributes)))
for _, ext := range a.ExtendedAttributes {
ext.MarshalInto(b)
ext.MarshalInto(buf)
}
}
}
@ -156,74 +156,51 @@ func (a *Attributes) MarshalBinary() ([]byte, error) {
// UnmarshalFrom unmarshals an Attributes from the given Buffer into e.
//
// NOTE: The values of fields not covered in the a.Flags are explicitly undefined.
func (a *Attributes) UnmarshalFrom(b *Buffer) (err error) {
flags, err := b.ConsumeUint32()
if err != nil {
return err
}
func (a *Attributes) UnmarshalFrom(buf *Buffer) (err error) {
flags := buf.ConsumeUint32()
return a.XXX_UnmarshalByFlags(flags, b)
return a.XXX_UnmarshalByFlags(flags, buf)
}
// XXX_UnmarshalByFlags uses the pre-existing a.Flags field to determine which fields to decode.
// DO NOT USE THIS: it is an anti-corruption function to implement existing internal usage in pkg/sftp.
// This function is not a part of any compatibility promise.
func (a *Attributes) XXX_UnmarshalByFlags(flags uint32, b *Buffer) (err error) {
func (a *Attributes) XXX_UnmarshalByFlags(flags uint32, buf *Buffer) (err error) {
a.Flags = flags
// Short-circuit dummy attributes.
if a.Flags == 0 {
return nil
return buf.Err
}
if a.Flags&AttrSize != 0 {
if a.Size, err = b.ConsumeUint64(); err != nil {
return err
}
a.Size = buf.ConsumeUint64()
}
if a.Flags&AttrUIDGID != 0 {
if a.UID, err = b.ConsumeUint32(); err != nil {
return err
}
if a.GID, err = b.ConsumeUint32(); err != nil {
return err
}
a.UID = buf.ConsumeUint32()
a.GID = buf.ConsumeUint32()
}
if a.Flags&AttrPermissions != 0 {
m, err := b.ConsumeUint32()
if err != nil {
return err
}
a.Permissions = FileMode(m)
a.Permissions = FileMode(buf.ConsumeUint32())
}
if a.Flags&AttrACModTime != 0 {
if a.ATime, err = b.ConsumeUint32(); err != nil {
return err
}
if a.MTime, err = b.ConsumeUint32(); err != nil {
return err
}
a.ATime = buf.ConsumeUint32()
a.MTime = buf.ConsumeUint32()
}
if a.Flags&AttrExtended != 0 {
count, err := b.ConsumeUint32()
if err != nil {
return err
}
count := buf.ConsumeCount()
a.ExtendedAttributes = make([]ExtendedAttribute, count)
for i := range a.ExtendedAttributes {
a.ExtendedAttributes[i].UnmarshalFrom(b)
a.ExtendedAttributes[i].UnmarshalFrom(buf)
}
}
return nil
return buf.Err
}
// UnmarshalBinary decodes the binary encoding of Attributes into e.
@ -233,7 +210,7 @@ func (a *Attributes) UnmarshalBinary(data []byte) error {
// ExtendedAttribute defines the extended file attribute type defined in draft-ietf-secsh-filexfer-02
//
// Defined in: https://tools.ietf.org/html/draft-ietf-secsh-filexfer-02#section-5
// Defined in: https://filezilla-project.org/specs/draft-ietf-secsh-filexfer-02.txt#section-5
type ExtendedAttribute struct {
Type string
Data string
@ -245,9 +222,9 @@ func (e *ExtendedAttribute) Len() int {
}
// MarshalInto marshals e onto the end of the given Buffer.
func (e *ExtendedAttribute) MarshalInto(b *Buffer) {
b.AppendString(e.Type)
b.AppendString(e.Data)
func (e *ExtendedAttribute) MarshalInto(buf *Buffer) {
buf.AppendString(e.Type)
buf.AppendString(e.Data)
}
// MarshalBinary returns e as the binary encoding of e.
@ -258,16 +235,13 @@ func (e *ExtendedAttribute) MarshalBinary() ([]byte, error) {
}
// UnmarshalFrom unmarshals an ExtendedAattribute from the given Buffer into e.
func (e *ExtendedAttribute) UnmarshalFrom(b *Buffer) (err error) {
if e.Type, err = b.ConsumeString(); err != nil {
return err
func (e *ExtendedAttribute) UnmarshalFrom(buf *Buffer) (err error) {
*e = ExtendedAttribute{
Type: buf.ConsumeString(),
Data: buf.ConsumeString(),
}
if e.Data, err = b.ConsumeString(); err != nil {
return err
}
return nil
return buf.Err
}
// UnmarshalBinary decodes the binary encoding of ExtendedAttribute into e.
@ -290,11 +264,11 @@ func (e *NameEntry) Len() int {
}
// MarshalInto marshals e onto the end of the given Buffer.
func (e *NameEntry) MarshalInto(b *Buffer) {
b.AppendString(e.Filename)
b.AppendString(e.Longname)
func (e *NameEntry) MarshalInto(buf *Buffer) {
buf.AppendString(e.Filename)
buf.AppendString(e.Longname)
e.Attrs.MarshalInto(b)
e.Attrs.MarshalInto(buf)
}
// MarshalBinary returns e as the binary encoding of e.
@ -307,16 +281,13 @@ func (e *NameEntry) MarshalBinary() ([]byte, error) {
// UnmarshalFrom unmarshals an NameEntry from the given Buffer into e.
//
// NOTE: The values of fields not covered in the a.Flags are explicitly undefined.
func (e *NameEntry) UnmarshalFrom(b *Buffer) (err error) {
if e.Filename, err = b.ConsumeString(); err != nil {
return err
func (e *NameEntry) UnmarshalFrom(buf *Buffer) (err error) {
*e = NameEntry{
Filename: buf.ConsumeString(),
Longname: buf.ConsumeString(),
}
if e.Longname, err = b.ConsumeString(); err != nil {
return err
}
return e.Attrs.UnmarshalFrom(b)
return e.Attrs.UnmarshalFrom(buf)
}
// UnmarshalBinary decodes the binary encoding of NameEntry into e.

View file

@ -1,4 +1,4 @@
package filexfer
package sshfx
import (
"encoding/binary"
@ -17,6 +17,7 @@ var (
type Buffer struct {
b []byte
off int
Err error
}
// NewBuffer creates and initializes a new buffer using buf as its initial contents.
@ -51,14 +52,17 @@ func (b *Buffer) Cap() int { return cap(b.b) }
// Reset resets the buffer to be empty, but it retains the underlying storage for use by future Appends.
func (b *Buffer) Reset() {
b.b = b.b[:0]
b.off = 0
*b = Buffer{
b: b.b[:0],
}
}
// StartPacket resets and initializes the buffer to be ready to start marshaling a packet into.
// It truncates the buffer, reserves space for uint32(length), then appends the given packetType and requestID.
func (b *Buffer) StartPacket(packetType PacketType, requestID uint32) {
b.b, b.off = append(b.b[:0], make([]byte, 4)...), 0
*b = Buffer{
b: append(b.b[:0], make([]byte, 4)...),
}
b.AppendUint8(uint8(packetType))
b.AppendUint32(requestID)
@ -81,15 +85,21 @@ func (b *Buffer) Packet(payload []byte) (header, payloadPassThru []byte, err err
}
// ConsumeUint8 consumes a single byte from the buffer.
// If the buffer does not have enough data, it will return ErrShortPacket.
func (b *Buffer) ConsumeUint8() (uint8, error) {
// If the buffer does not have enough data, it will set Err to ErrShortPacket.
func (b *Buffer) ConsumeUint8() uint8 {
if b.Err != nil {
return 0
}
if b.Len() < 1 {
return 0, ErrShortPacket
b.off = len(b.b)
b.Err = ErrShortPacket
return 0
}
var v uint8
v, b.off = b.b[b.off], b.off+1
return v, nil
return v
}
// AppendUint8 appends a single byte into the buffer.
@ -98,14 +108,9 @@ func (b *Buffer) AppendUint8(v uint8) {
}
// ConsumeBool consumes a single byte from the buffer, and returns true if that byte is non-zero.
// If the buffer does not have enough data, it will return ErrShortPacket.
func (b *Buffer) ConsumeBool() (bool, error) {
v, err := b.ConsumeUint8()
if err != nil {
return false, err
}
return v != 0, nil
// If the buffer does not have enough data, it will set Err to ErrShortPacket.
func (b *Buffer) ConsumeBool() bool {
return b.ConsumeUint8() != 0
}
// AppendBool appends a single bool into the buffer.
@ -119,15 +124,21 @@ func (b *Buffer) AppendBool(v bool) {
}
// ConsumeUint16 consumes a single uint16 from the buffer, in network byte order (big-endian).
// If the buffer does not have enough data, it will return ErrShortPacket.
func (b *Buffer) ConsumeUint16() (uint16, error) {
// If the buffer does not have enough data, it will set Err to ErrShortPacket.
func (b *Buffer) ConsumeUint16() uint16 {
if b.Err != nil {
return 0
}
if b.Len() < 2 {
return 0, ErrShortPacket
b.off = len(b.b)
b.Err = ErrShortPacket
return 0
}
v := binary.BigEndian.Uint16(b.b[b.off:])
b.off += 2
return v, nil
return v
}
// AppendUint16 appends single uint16 into the buffer, in network byte order (big-endian).
@ -146,15 +157,21 @@ func unmarshalUint32(b []byte) uint32 {
}
// ConsumeUint32 consumes a single uint32 from the buffer, in network byte order (big-endian).
// If the buffer does not have enough data, it will return ErrShortPacket.
func (b *Buffer) ConsumeUint32() (uint32, error) {
// If the buffer does not have enough data, it will set Err to ErrShortPacket.
func (b *Buffer) ConsumeUint32() uint32 {
if b.Err != nil {
return 0
}
if b.Len() < 4 {
return 0, ErrShortPacket
b.off = len(b.b)
b.Err = ErrShortPacket
return 0
}
v := binary.BigEndian.Uint32(b.b[b.off:])
b.off += 4
return v, nil
return v
}
// AppendUint32 appends a single uint32 into the buffer, in network byte order (big-endian).
@ -167,16 +184,33 @@ func (b *Buffer) AppendUint32(v uint32) {
)
}
// ConsumeCount consumes a single uint32 count from the buffer, in network byte order (big-endian) as an int.
// If the buffer does not have enough data, it will set Err to ErrShortPacket.
func (b *Buffer) ConsumeCount() int {
return int(b.ConsumeUint32())
}
// AppendCount appends a single int length as a uint32 into the buffer, in network byte order (big-endian).
func (b *Buffer) AppendCount(v int) {
b.AppendUint32(uint32(v))
}
// ConsumeUint64 consumes a single uint64 from the buffer, in network byte order (big-endian).
// If the buffer does not have enough data, it will return ErrShortPacket.
func (b *Buffer) ConsumeUint64() (uint64, error) {
// If the buffer does not have enough data, it will set Err to ErrShortPacket.
func (b *Buffer) ConsumeUint64() uint64 {
if b.Err != nil {
return 0
}
if b.Len() < 8 {
return 0, ErrShortPacket
b.off = len(b.b)
b.Err = ErrShortPacket
return 0
}
v := binary.BigEndian.Uint64(b.b[b.off:])
b.off += 8
return v, nil
return v
}
// AppendUint64 appends a single uint64 into the buffer, in network byte order (big-endian).
@ -194,14 +228,9 @@ func (b *Buffer) AppendUint64(v uint64) {
}
// ConsumeInt64 consumes a single int64 from the buffer, in network byte order (big-endian) with twos complement.
// If the buffer does not have enough data, it will return ErrShortPacket.
func (b *Buffer) ConsumeInt64() (int64, error) {
u, err := b.ConsumeUint64()
if err != nil {
return 0, err
}
return int64(u), err
// If the buffer does not have enough data, it will set Err to ErrShortPacket.
func (b *Buffer) ConsumeInt64() int64 {
return int64(b.ConsumeUint64())
}
// AppendInt64 appends a single int64 into the buffer, in network byte order (big-endian) with twos complement.
@ -211,29 +240,52 @@ func (b *Buffer) AppendInt64(v int64) {
// ConsumeByteSlice consumes a single string of raw binary data from the buffer.
// A string is a uint32 length, followed by that number of raw bytes.
// If the buffer does not have enough data, or defines a length larger than available, it will return ErrShortPacket.
// If the buffer does not have enough data, or defines a length larger than available, it will set Err to ErrShortPacket.
//
// The returned slice aliases the buffer contents, and is valid only as long as the buffer is not reused
// (that is, only until the next call to Reset, PutLength, StartPacket, or UnmarshalBinary).
//
// In no case will any Consume calls return overlapping slice aliases,
// and Append calls are guaranteed to not disturb this slice alias.
func (b *Buffer) ConsumeByteSlice() ([]byte, error) {
length, err := b.ConsumeUint32()
if err != nil {
return nil, err
func (b *Buffer) ConsumeByteSlice() []byte {
length := int(b.ConsumeUint32())
if b.Err != nil {
return nil
}
if b.Len() < int(length) {
return nil, ErrShortPacket
if b.Len() < length || length < 0 {
b.off = len(b.b)
b.Err = ErrShortPacket
return nil
}
v := b.b[b.off:]
if len(v) > int(length) {
if len(v) > length || cap(v) > length {
v = v[:length:length]
}
b.off += int(length)
return v, nil
return v
}
// ConsumeByteSliceCopy consumes a single string of raw binary data as a copy from the buffer.
// A string is a uint32 length, followed by that number of raw bytes.
// If the buffer does not have enough data, or defines a length larger than available, it will set Err to ErrShortPacket.
//
// The returned slice does not alias any buffer contents,
// and will therefore be valid even if the buffer is later reused.
//
// If hint has sufficient capacity to hold the data, it will be reused and overwritten,
// otherwise a new backing slice will be allocated and returned.
func (b *Buffer) ConsumeByteSliceCopy(hint []byte) []byte {
data := b.ConsumeByteSlice()
if grow := len(data) - len(hint); grow > 0 {
hint = append(hint, make([]byte, grow)...)
}
n := copy(hint, data)
hint = hint[:n]
return hint
}
// AppendByteSlice appends a single string of raw binary data into the buffer.
@ -245,17 +297,12 @@ func (b *Buffer) AppendByteSlice(v []byte) {
// ConsumeString consumes a single string of binary data from the buffer.
// A string is a uint32 length, followed by that number of raw bytes.
// If the buffer does not have enough data, or defines a length larger than available, it will return ErrShortPacket.
// If the buffer does not have enough data, or defines a length larger than available, it will set Err to ErrShortPacket.
//
// NOTE: Go implicitly assumes that strings contain UTF-8 encoded data.
// All caveats on using arbitrary binary data in Go strings applies.
func (b *Buffer) ConsumeString() (string, error) {
v, err := b.ConsumeByteSlice()
if err != nil {
return "", err
}
return string(v), nil
func (b *Buffer) ConsumeString() string {
return string(b.ConsumeByteSlice())
}
// AppendString appends a single string of binary data into the buffer.

View file

@ -1,4 +1,4 @@
package filexfer
package sshfx
import (
"encoding"
@ -86,8 +86,9 @@ func (p *ExtendedPacket) MarshalPacket(reqid uint32, b []byte) (header, payload
// If the extension has not been registered, then a new Buffer will be allocated.
// Then the request-specific-data will be unmarshaled from the rest of the buffer.
func (p *ExtendedPacket) UnmarshalPacketBody(buf *Buffer) (err error) {
if p.ExtendedRequest, err = buf.ConsumeString(); err != nil {
return err
p.ExtendedRequest = buf.ConsumeString()
if buf.Err != nil {
return buf.Err
}
if p.Data == nil {

View file

@ -1,4 +1,4 @@
package filexfer
package sshfx
// ExtensionPair defines the extension-pair type defined in draft-ietf-secsh-filexfer-13.
// This type is backwards-compatible with how draft-ietf-secsh-filexfer-02 defines extensions.
@ -29,15 +29,12 @@ func (e *ExtensionPair) MarshalBinary() ([]byte, error) {
// UnmarshalFrom unmarshals an ExtensionPair from the given Buffer into e.
func (e *ExtensionPair) UnmarshalFrom(buf *Buffer) (err error) {
if e.Name, err = buf.ConsumeString(); err != nil {
return err
*e = ExtensionPair{
Name: buf.ConsumeString(),
Data: buf.ConsumeString(),
}
if e.Data, err = buf.ConsumeString(); err != nil {
return err
}
return nil
return buf.Err
}
// UnmarshalBinary decodes the binary encoding of ExtensionPair into e.

View file

@ -1,5 +1,5 @@
// Package filexfer implements the wire encoding for secsh-filexfer as described in https://tools.ietf.org/html/draft-ietf-secsh-filexfer-02
package filexfer
// Package sshfx implements the wire encoding for secsh-filexfer as described in https://filezilla-project.org/specs/draft-ietf-secsh-filexfer-02.txt
package sshfx
// PacketMarshaller narrowly defines packets that will only be transmitted.
//

View file

@ -1,4 +1,4 @@
package filexfer
package sshfx
import (
"fmt"
@ -10,7 +10,7 @@ type Status uint32
// Defines the various SSH_FX_* values.
const (
// see draft-ietf-secsh-filexfer-02
// https://tools.ietf.org/html/draft-ietf-secsh-filexfer-02#section-7
// https://filezilla-project.org/specs/draft-ietf-secsh-filexfer-02.txt#section-7
StatusOK = Status(iota)
StatusEOF
StatusNoSuchFile
@ -21,28 +21,28 @@ const (
StatusConnectionLost
StatusOPUnsupported
// https://tools.ietf.org/html/draft-ietf-secsh-filexfer-03#section-7
// https://filezilla-project.org/specs/draft-ietf-secsh-filexfer-03.txt#section-7
StatusV4InvalidHandle
StatusV4NoSuchPath
StatusV4FileAlreadyExists
StatusV4WriteProtect
// https://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-7
// https://filezilla-project.org/specs/draft-ietf-secsh-filexfer-04.txt#section-7
StatusV4NoMedia
// https://tools.ietf.org/html/draft-ietf-secsh-filexfer-05#section-7
// https://filezilla-project.org/specs/draft-ietf-secsh-filexfer-05.txt#section-7
StatusV5NoSpaceOnFilesystem
StatusV5QuotaExceeded
StatusV5UnknownPrincipal
StatusV5LockConflict
// https://tools.ietf.org/html/draft-ietf-secsh-filexfer-06#section-8
// https://filezilla-project.org/specs/draft-ietf-secsh-filexfer-06.txt#section-8
StatusV6DirNotEmpty
StatusV6NotADirectory
StatusV6InvalidFilename
StatusV6LinkLoop
// https://tools.ietf.org/html/draft-ietf-secsh-filexfer-07#section-8
// https://filezilla-project.org/specs/draft-ietf-secsh-filexfer-07.txt#section-8
StatusV6CannotDelete
StatusV6InvalidParameter
StatusV6FileIsADirectory
@ -50,10 +50,10 @@ const (
StatusV6ByteRangeLockRefused
StatusV6DeletePending
// https://tools.ietf.org/html/draft-ietf-secsh-filexfer-08#section-8.1
// https://filezilla-project.org/specs/draft-ietf-secsh-filexfer-08.txt#section-8.1
StatusV6FileCorrupt
// https://tools.ietf.org/html/draft-ietf-secsh-filexfer-10#section-9.1
// https://filezilla-project.org/specs/draft-ietf-secsh-filexfer-10.txt#section-9.1
StatusV6OwnerInvalid
StatusV6GroupInvalid

View file

@ -1,4 +1,4 @@
package filexfer
package sshfx
import (
"fmt"
@ -9,7 +9,7 @@ type PacketType uint8
// Request packet types.
const (
// https://tools.ietf.org/html/draft-ietf-secsh-filexfer-02#section-3
// https://filezilla-project.org/specs/draft-ietf-secsh-filexfer-02.txt#section-3
PacketTypeInit = PacketType(iota + 1)
PacketTypeVersion
PacketTypeOpen
@ -31,17 +31,17 @@ const (
PacketTypeReadLink
PacketTypeSymlink
// https://tools.ietf.org/html/draft-ietf-secsh-filexfer-07#section-3.3
// https://filezilla-project.org/specs/draft-ietf-secsh-filexfer-07.txt#section-3.3
PacketTypeV6Link
// https://tools.ietf.org/html/draft-ietf-secsh-filexfer-08#section-3.3
// https://filezilla-project.org/specs/draft-ietf-secsh-filexfer-08.txt#section-3.3
PacketTypeV6Block
PacketTypeV6Unblock
)
// Response packet types.
const (
// https://tools.ietf.org/html/draft-ietf-secsh-filexfer-02#section-3
// https://filezilla-project.org/specs/draft-ietf-secsh-filexfer-02.txt#section-3
PacketTypeStatus = PacketType(iota + 101)
PacketTypeHandle
PacketTypeData
@ -51,7 +51,7 @@ const (
// Extended packet types.
const (
// https://tools.ietf.org/html/draft-ietf-secsh-filexfer-02#section-3
// https://filezilla-project.org/specs/draft-ietf-secsh-filexfer-02.txt#section-3
PacketTypeExtended = PacketType(iota + 200)
PacketTypeExtendedReply
)
@ -122,3 +122,48 @@ func (f PacketType) String() string {
return fmt.Sprintf("SSH_FXP_UNKNOWN(%d)", f)
}
}
func newPacketFromType(typ PacketType) (Packet, error) {
switch typ {
case PacketTypeOpen:
return new(OpenPacket), nil
case PacketTypeClose:
return new(ClosePacket), nil
case PacketTypeRead:
return new(ReadPacket), nil
case PacketTypeWrite:
return new(WritePacket), nil
case PacketTypeLStat:
return new(LStatPacket), nil
case PacketTypeFStat:
return new(FStatPacket), nil
case PacketTypeSetstat:
return new(SetstatPacket), nil
case PacketTypeFSetstat:
return new(FSetstatPacket), nil
case PacketTypeOpenDir:
return new(OpenDirPacket), nil
case PacketTypeReadDir:
return new(ReadDirPacket), nil
case PacketTypeRemove:
return new(RemovePacket), nil
case PacketTypeMkdir:
return new(MkdirPacket), nil
case PacketTypeRmdir:
return new(RmdirPacket), nil
case PacketTypeRealPath:
return new(RealPathPacket), nil
case PacketTypeStat:
return new(StatPacket), nil
case PacketTypeRename:
return new(RenamePacket), nil
case PacketTypeReadLink:
return new(ReadLinkPacket), nil
case PacketTypeSymlink:
return new(SymlinkPacket), nil
case PacketTypeExtended:
return new(ExtendedPacket), nil
default:
return nil, fmt.Errorf("unexpected request packet type: %v", typ)
}
}

View file

@ -1,4 +1,4 @@
package filexfer
package sshfx
// ClosePacket defines the SSH_FXP_CLOSE packet.
type ClosePacket struct {
@ -27,18 +27,18 @@ func (p *ClosePacket) MarshalPacket(reqid uint32, b []byte) (header, payload []b
// UnmarshalPacketBody unmarshals the packet body from the given Buffer.
// It is assumed that the uint32(request-id) has already been consumed.
func (p *ClosePacket) UnmarshalPacketBody(buf *Buffer) (err error) {
if p.Handle, err = buf.ConsumeString(); err != nil {
return err
*p = ClosePacket{
Handle: buf.ConsumeString(),
}
return nil
return buf.Err
}
// ReadPacket defines the SSH_FXP_READ packet.
type ReadPacket struct {
Handle string
Offset uint64
Len uint32
Length uint32
}
// Type returns the SSH_FXP_xy value associated with this packet type.
@ -58,7 +58,7 @@ func (p *ReadPacket) MarshalPacket(reqid uint32, b []byte) (header, payload []by
buf.StartPacket(PacketTypeRead, reqid)
buf.AppendString(p.Handle)
buf.AppendUint64(p.Offset)
buf.AppendUint32(p.Len)
buf.AppendUint32(p.Length)
return buf.Packet(payload)
}
@ -66,19 +66,13 @@ func (p *ReadPacket) MarshalPacket(reqid uint32, b []byte) (header, payload []by
// UnmarshalPacketBody unmarshals the packet body from the given Buffer.
// It is assumed that the uint32(request-id) has already been consumed.
func (p *ReadPacket) UnmarshalPacketBody(buf *Buffer) (err error) {
if p.Handle, err = buf.ConsumeString(); err != nil {
return err
*p = ReadPacket{
Handle: buf.ConsumeString(),
Offset: buf.ConsumeUint64(),
Length: buf.ConsumeUint32(),
}
if p.Offset, err = buf.ConsumeUint64(); err != nil {
return err
}
if p.Len, err = buf.ConsumeUint32(); err != nil {
return err
}
return nil
return buf.Err
}
// WritePacket defines the SSH_FXP_WRITE packet.
@ -121,26 +115,13 @@ func (p *WritePacket) MarshalPacket(reqid uint32, b []byte) (header, payload []b
//
// This means this _does not_ alias any of the data buffer that is passed in.
func (p *WritePacket) UnmarshalPacketBody(buf *Buffer) (err error) {
if p.Handle, err = buf.ConsumeString(); err != nil {
return err
*p = WritePacket{
Handle: buf.ConsumeString(),
Offset: buf.ConsumeUint64(),
Data: buf.ConsumeByteSliceCopy(p.Data),
}
if p.Offset, err = buf.ConsumeUint64(); err != nil {
return err
}
data, err := buf.ConsumeByteSlice()
if err != nil {
return err
}
if len(p.Data) < len(data) {
p.Data = make([]byte, len(data))
}
n := copy(p.Data, data)
p.Data = p.Data[:n]
return nil
return buf.Err
}
// FStatPacket defines the SSH_FXP_FSTAT packet.
@ -170,11 +151,11 @@ func (p *FStatPacket) MarshalPacket(reqid uint32, b []byte) (header, payload []b
// UnmarshalPacketBody unmarshals the packet body from the given Buffer.
// It is assumed that the uint32(request-id) has already been consumed.
func (p *FStatPacket) UnmarshalPacketBody(buf *Buffer) (err error) {
if p.Handle, err = buf.ConsumeString(); err != nil {
return err
*p = FStatPacket{
Handle: buf.ConsumeString(),
}
return nil
return buf.Err
}
// FSetstatPacket defines the SSH_FXP_FSETSTAT packet.
@ -207,8 +188,8 @@ func (p *FSetstatPacket) MarshalPacket(reqid uint32, b []byte) (header, payload
// UnmarshalPacketBody unmarshals the packet body from the given Buffer.
// It is assumed that the uint32(request-id) has already been consumed.
func (p *FSetstatPacket) UnmarshalPacketBody(buf *Buffer) (err error) {
if p.Handle, err = buf.ConsumeString(); err != nil {
return err
*p = FSetstatPacket{
Handle: buf.ConsumeString(),
}
return p.Attrs.UnmarshalFrom(buf)
@ -241,9 +222,9 @@ func (p *ReadDirPacket) MarshalPacket(reqid uint32, b []byte) (header, payload [
// UnmarshalPacketBody unmarshals the packet body from the given Buffer.
// It is assumed that the uint32(request-id) has already been consumed.
func (p *ReadDirPacket) UnmarshalPacketBody(buf *Buffer) (err error) {
if p.Handle, err = buf.ConsumeString(); err != nil {
return err
*p = ReadDirPacket{
Handle: buf.ConsumeString(),
}
return nil
return buf.Err
}

View file

@ -1,4 +1,4 @@
package filexfer
package sshfx
// InitPacket defines the SSH_FXP_INIT packet.
type InitPacket struct {
@ -33,8 +33,8 @@ func (p *InitPacket) MarshalBinary() ([]byte, error) {
func (p *InitPacket) UnmarshalBinary(data []byte) (err error) {
buf := NewBuffer(data)
if p.Version, err = buf.ConsumeUint32(); err != nil {
return err
*p = InitPacket{
Version: buf.ConsumeUint32(),
}
for buf.Len() > 0 {
@ -46,7 +46,7 @@ func (p *InitPacket) UnmarshalBinary(data []byte) (err error) {
p.Extensions = append(p.Extensions, &ext)
}
return nil
return buf.Err
}
// VersionPacket defines the SSH_FXP_VERSION packet.
@ -82,8 +82,8 @@ func (p *VersionPacket) MarshalBinary() ([]byte, error) {
func (p *VersionPacket) UnmarshalBinary(data []byte) (err error) {
buf := NewBuffer(data)
if p.Version, err = buf.ConsumeUint32(); err != nil {
return err
*p = VersionPacket{
Version: buf.ConsumeUint32(),
}
for buf.Len() > 0 {

View file

@ -1,4 +1,4 @@
package filexfer
package sshfx
// SSH_FXF_* flags.
const (
@ -43,12 +43,9 @@ func (p *OpenPacket) MarshalPacket(reqid uint32, b []byte) (header, payload []by
// UnmarshalPacketBody unmarshals the packet body from the given Buffer.
// It is assumed that the uint32(request-id) has already been consumed.
func (p *OpenPacket) UnmarshalPacketBody(buf *Buffer) (err error) {
if p.Filename, err = buf.ConsumeString(); err != nil {
return err
}
if p.PFlags, err = buf.ConsumeUint32(); err != nil {
return err
*p = OpenPacket{
Filename: buf.ConsumeString(),
PFlags: buf.ConsumeUint32(),
}
return p.Attrs.UnmarshalFrom(buf)
@ -81,9 +78,9 @@ func (p *OpenDirPacket) MarshalPacket(reqid uint32, b []byte) (header, payload [
// UnmarshalPacketBody unmarshals the packet body from the given Buffer.
// It is assumed that the uint32(request-id) has already been consumed.
func (p *OpenDirPacket) UnmarshalPacketBody(buf *Buffer) (err error) {
if p.Path, err = buf.ConsumeString(); err != nil {
return err
*p = OpenDirPacket{
Path: buf.ConsumeString(),
}
return nil
return buf.Err
}

View file

@ -1,59 +1,13 @@
package filexfer
package sshfx
import (
"errors"
"fmt"
"io"
)
// smallBufferSize is an initial allocation minimal capacity.
const smallBufferSize = 64
func newPacketFromType(typ PacketType) (Packet, error) {
switch typ {
case PacketTypeOpen:
return new(OpenPacket), nil
case PacketTypeClose:
return new(ClosePacket), nil
case PacketTypeRead:
return new(ReadPacket), nil
case PacketTypeWrite:
return new(WritePacket), nil
case PacketTypeLStat:
return new(LStatPacket), nil
case PacketTypeFStat:
return new(FStatPacket), nil
case PacketTypeSetstat:
return new(SetstatPacket), nil
case PacketTypeFSetstat:
return new(FSetstatPacket), nil
case PacketTypeOpenDir:
return new(OpenDirPacket), nil
case PacketTypeReadDir:
return new(ReadDirPacket), nil
case PacketTypeRemove:
return new(RemovePacket), nil
case PacketTypeMkdir:
return new(MkdirPacket), nil
case PacketTypeRmdir:
return new(RmdirPacket), nil
case PacketTypeRealPath:
return new(RealPathPacket), nil
case PacketTypeStat:
return new(StatPacket), nil
case PacketTypeRename:
return new(RenamePacket), nil
case PacketTypeReadLink:
return new(ReadLinkPacket), nil
case PacketTypeSymlink:
return new(SymlinkPacket), nil
case PacketTypeExtended:
return new(ExtendedPacket), nil
default:
return nil, fmt.Errorf("unexpected request packet type: %v", typ)
}
}
// RawPacket implements the general packet format from draft-ietf-secsh-filexfer-02
//
// RawPacket is intended for use in clients receiving responses,
@ -63,7 +17,7 @@ func newPacketFromType(typ PacketType) (Packet, error) {
// For servers expecting to receive arbitrary request packet types,
// use RequestPacket.
//
// Defined in https://tools.ietf.org/html/draft-ietf-secsh-filexfer-02#section-3
// Defined in https://filezilla-project.org/specs/draft-ietf-secsh-filexfer-02.txt#section-3
type RawPacket struct {
PacketType PacketType
RequestID uint32
@ -110,19 +64,14 @@ func (p *RawPacket) MarshalBinary() ([]byte, error) {
// The Data field will alias the passed in Buffer,
// so the buffer passed in should not be reused before RawPacket.Reset().
func (p *RawPacket) UnmarshalFrom(buf *Buffer) error {
typ, err := buf.ConsumeUint8()
if err != nil {
return err
}
p.PacketType = PacketType(typ)
if p.RequestID, err = buf.ConsumeUint32(); err != nil {
return err
*p = RawPacket{
PacketType: PacketType(buf.ConsumeUint8()),
RequestID: buf.ConsumeUint32(),
}
p.Data = *buf
return nil
return buf.Err
}
// UnmarshalBinary decodes a full raw packet out of the given data.
@ -225,7 +174,7 @@ func (p *RawPacket) ReadFrom(r io.Reader, b []byte, maxPacketLength uint32) erro
// where automatic unmarshaling of the packet body does not make sense,
// use RawPacket.
//
// Defined in https://tools.ietf.org/html/draft-ietf-secsh-filexfer-02#section-3
// Defined in https://filezilla-project.org/specs/draft-ietf-secsh-filexfer-02.txt#section-3
type RequestPacket struct {
RequestID uint32
@ -268,18 +217,19 @@ func (p *RequestPacket) MarshalBinary() ([]byte, error) {
// The Request field may alias the passed in Buffer, (e.g. SSH_FXP_WRITE),
// so the buffer passed in should not be reused before RequestPacket.Reset().
func (p *RequestPacket) UnmarshalFrom(buf *Buffer) error {
typ, err := buf.ConsumeUint8()
typ := PacketType(buf.ConsumeUint8())
if buf.Err != nil {
return buf.Err
}
req, err := newPacketFromType(typ)
if err != nil {
return err
}
p.Request, err = newPacketFromType(PacketType(typ))
if err != nil {
return err
}
if p.RequestID, err = buf.ConsumeUint32(); err != nil {
return err
*p = RequestPacket{
RequestID: buf.ConsumeUint32(),
Request: req,
}
return p.Request.UnmarshalPacketBody(buf)

View file

@ -1,4 +1,4 @@
package filexfer
package sshfx
// LStatPacket defines the SSH_FXP_LSTAT packet.
type LStatPacket struct {
@ -27,11 +27,11 @@ func (p *LStatPacket) MarshalPacket(reqid uint32, b []byte) (header, payload []b
// UnmarshalPacketBody unmarshals the packet body from the given Buffer.
// It is assumed that the uint32(request-id) has already been consumed.
func (p *LStatPacket) UnmarshalPacketBody(buf *Buffer) (err error) {
if p.Path, err = buf.ConsumeString(); err != nil {
return err
*p = LStatPacket{
Path: buf.ConsumeString(),
}
return nil
return buf.Err
}
// SetstatPacket defines the SSH_FXP_SETSTAT packet.
@ -64,8 +64,8 @@ func (p *SetstatPacket) MarshalPacket(reqid uint32, b []byte) (header, payload [
// UnmarshalPacketBody unmarshals the packet body from the given Buffer.
// It is assumed that the uint32(request-id) has already been consumed.
func (p *SetstatPacket) UnmarshalPacketBody(buf *Buffer) (err error) {
if p.Path, err = buf.ConsumeString(); err != nil {
return err
*p = SetstatPacket{
Path: buf.ConsumeString(),
}
return p.Attrs.UnmarshalFrom(buf)
@ -98,11 +98,11 @@ func (p *RemovePacket) MarshalPacket(reqid uint32, b []byte) (header, payload []
// UnmarshalPacketBody unmarshals the packet body from the given Buffer.
// It is assumed that the uint32(request-id) has already been consumed.
func (p *RemovePacket) UnmarshalPacketBody(buf *Buffer) (err error) {
if p.Path, err = buf.ConsumeString(); err != nil {
return err
*p = RemovePacket{
Path: buf.ConsumeString(),
}
return nil
return buf.Err
}
// MkdirPacket defines the SSH_FXP_MKDIR packet.
@ -135,8 +135,8 @@ func (p *MkdirPacket) MarshalPacket(reqid uint32, b []byte) (header, payload []b
// UnmarshalPacketBody unmarshals the packet body from the given Buffer.
// It is assumed that the uint32(request-id) has already been consumed.
func (p *MkdirPacket) UnmarshalPacketBody(buf *Buffer) (err error) {
if p.Path, err = buf.ConsumeString(); err != nil {
return err
*p = MkdirPacket{
Path: buf.ConsumeString(),
}
return p.Attrs.UnmarshalFrom(buf)
@ -169,11 +169,11 @@ func (p *RmdirPacket) MarshalPacket(reqid uint32, b []byte) (header, payload []b
// UnmarshalPacketBody unmarshals the packet body from the given Buffer.
// It is assumed that the uint32(request-id) has already been consumed.
func (p *RmdirPacket) UnmarshalPacketBody(buf *Buffer) (err error) {
if p.Path, err = buf.ConsumeString(); err != nil {
return err
*p = RmdirPacket{
Path: buf.ConsumeString(),
}
return nil
return buf.Err
}
// RealPathPacket defines the SSH_FXP_REALPATH packet.
@ -203,11 +203,11 @@ func (p *RealPathPacket) MarshalPacket(reqid uint32, b []byte) (header, payload
// UnmarshalPacketBody unmarshals the packet body from the given Buffer.
// It is assumed that the uint32(request-id) has already been consumed.
func (p *RealPathPacket) UnmarshalPacketBody(buf *Buffer) (err error) {
if p.Path, err = buf.ConsumeString(); err != nil {
return err
*p = RealPathPacket{
Path: buf.ConsumeString(),
}
return nil
return buf.Err
}
// StatPacket defines the SSH_FXP_STAT packet.
@ -237,11 +237,11 @@ func (p *StatPacket) MarshalPacket(reqid uint32, b []byte) (header, payload []by
// UnmarshalPacketBody unmarshals the packet body from the given Buffer.
// It is assumed that the uint32(request-id) has already been consumed.
func (p *StatPacket) UnmarshalPacketBody(buf *Buffer) (err error) {
if p.Path, err = buf.ConsumeString(); err != nil {
return err
*p = StatPacket{
Path: buf.ConsumeString(),
}
return nil
return buf.Err
}
// RenamePacket defines the SSH_FXP_RENAME packet.
@ -274,15 +274,12 @@ func (p *RenamePacket) MarshalPacket(reqid uint32, b []byte) (header, payload []
// UnmarshalPacketBody unmarshals the packet body from the given Buffer.
// It is assumed that the uint32(request-id) has already been consumed.
func (p *RenamePacket) UnmarshalPacketBody(buf *Buffer) (err error) {
if p.OldPath, err = buf.ConsumeString(); err != nil {
return err
*p = RenamePacket{
OldPath: buf.ConsumeString(),
NewPath: buf.ConsumeString(),
}
if p.NewPath, err = buf.ConsumeString(); err != nil {
return err
}
return nil
return buf.Err
}
// ReadLinkPacket defines the SSH_FXP_READLINK packet.
@ -312,18 +309,18 @@ func (p *ReadLinkPacket) MarshalPacket(reqid uint32, b []byte) (header, payload
// UnmarshalPacketBody unmarshals the packet body from the given Buffer.
// It is assumed that the uint32(request-id) has already been consumed.
func (p *ReadLinkPacket) UnmarshalPacketBody(buf *Buffer) (err error) {
if p.Path, err = buf.ConsumeString(); err != nil {
return err
*p = ReadLinkPacket{
Path: buf.ConsumeString(),
}
return nil
return buf.Err
}
// SymlinkPacket defines the SSH_FXP_SYMLINK packet.
//
// The order of the arguments to the SSH_FXP_SYMLINK method was inadvertently reversed.
// Unfortunately, the reversal was not noticed until the server was widely deployed.
// Covered in Section 3.1 of https://github.com/openssh/openssh-portable/blob/master/PROTOCOL
// Covered in Section 4.1 of https://github.com/openssh/openssh-portable/blob/master/PROTOCOL
type SymlinkPacket struct {
LinkPath string
TargetPath string
@ -355,14 +352,11 @@ func (p *SymlinkPacket) MarshalPacket(reqid uint32, b []byte) (header, payload [
// UnmarshalPacketBody unmarshals the packet body from the given Buffer.
// It is assumed that the uint32(request-id) has already been consumed.
func (p *SymlinkPacket) UnmarshalPacketBody(buf *Buffer) (err error) {
// Arguments were inadvertently reversed.
if p.TargetPath, err = buf.ConsumeString(); err != nil {
return err
*p = SymlinkPacket{
// Arguments were inadvertently reversed.
TargetPath: buf.ConsumeString(),
LinkPath: buf.ConsumeString(),
}
if p.LinkPath, err = buf.ConsumeString(); err != nil {
return err
}
return nil
return buf.Err
}

View file

@ -1,4 +1,4 @@
package filexfer
package sshfx
// FileMode represents a files mode and permission bits.
// The bits are defined according to POSIX standards,

View file

@ -1,4 +1,4 @@
package filexfer
package sshfx
import (
"fmt"
@ -6,7 +6,7 @@ import (
// StatusPacket defines the SSH_FXP_STATUS packet.
//
// Specified in https://tools.ietf.org/html/draft-ietf-secsh-filexfer-02#section-7
// Specified in https://filezilla-project.org/specs/draft-ietf-secsh-filexfer-02.txt#section-7
type StatusPacket struct {
StatusCode Status
ErrorMessage string
@ -19,7 +19,7 @@ func (p *StatusPacket) Error() string {
return "sftp: " + p.StatusCode.String()
}
return fmt.Sprintf("sftp: %q (%s)", p.ErrorMessage, p.StatusCode)
return fmt.Sprintf("sftp: %s: %q", p.StatusCode, p.ErrorMessage)
}
// Is returns true if target is a StatusPacket with the same StatusCode,
@ -57,21 +57,13 @@ func (p *StatusPacket) MarshalPacket(reqid uint32, b []byte) (header, payload []
// UnmarshalPacketBody unmarshals the packet body from the given Buffer.
// It is assumed that the uint32(request-id) has already been consumed.
func (p *StatusPacket) UnmarshalPacketBody(buf *Buffer) (err error) {
statusCode, err := buf.ConsumeUint32()
if err != nil {
return err
}
p.StatusCode = Status(statusCode)
if p.ErrorMessage, err = buf.ConsumeString(); err != nil {
return err
*p = StatusPacket{
StatusCode: Status(buf.ConsumeUint32()),
ErrorMessage: buf.ConsumeString(),
LanguageTag: buf.ConsumeString(),
}
if p.LanguageTag, err = buf.ConsumeString(); err != nil {
return err
}
return nil
return buf.Err
}
// HandlePacket defines the SSH_FXP_HANDLE packet.
@ -101,11 +93,11 @@ func (p *HandlePacket) MarshalPacket(reqid uint32, b []byte) (header, payload []
// UnmarshalPacketBody unmarshals the packet body from the given Buffer.
// It is assumed that the uint32(request-id) has already been consumed.
func (p *HandlePacket) UnmarshalPacketBody(buf *Buffer) (err error) {
if p.Handle, err = buf.ConsumeString(); err != nil {
return err
*p = HandlePacket{
Handle: buf.ConsumeString(),
}
return nil
return buf.Err
}
// DataPacket defines the SSH_FXP_DATA packet.
@ -143,18 +135,11 @@ func (p *DataPacket) MarshalPacket(reqid uint32, b []byte) (header, payload []by
//
// This means this _does not_ alias any of the data buffer that is passed in.
func (p *DataPacket) UnmarshalPacketBody(buf *Buffer) (err error) {
data, err := buf.ConsumeByteSlice()
if err != nil {
return err
*p = DataPacket{
Data: buf.ConsumeByteSliceCopy(p.Data),
}
if len(p.Data) < len(data) {
p.Data = make([]byte, len(data))
}
n := copy(p.Data, data)
p.Data = p.Data[:n]
return nil
return buf.Err
}
// NamePacket defines the SSH_FXP_NAME packet.
@ -193,14 +178,16 @@ func (p *NamePacket) MarshalPacket(reqid uint32, b []byte) (header, payload []by
// UnmarshalPacketBody unmarshals the packet body from the given Buffer.
// It is assumed that the uint32(request-id) has already been consumed.
func (p *NamePacket) UnmarshalPacketBody(buf *Buffer) (err error) {
count, err := buf.ConsumeUint32()
if err != nil {
return err
count := buf.ConsumeCount()
if buf.Err != nil {
return buf.Err
}
p.Entries = make([]*NameEntry, 0, count)
*p = NamePacket{
Entries: make([]*NameEntry, 0, count),
}
for i := uint32(0); i < count; i++ {
for i := 0; i < count; i++ {
var e NameEntry
if err := e.UnmarshalFrom(buf); err != nil {
return err
@ -209,7 +196,7 @@ func (p *NamePacket) UnmarshalPacketBody(buf *Buffer) (err error) {
p.Entries = append(p.Entries, &e)
}
return nil
return buf.Err
}
// AttrsPacket defines the SSH_FXP_ATTRS packet.

View file

@ -60,6 +60,13 @@ func runLs(idLookup NameLookupFileLister, dirent os.FileInfo) string {
uid = lsFormatID(sys.UID)
gid = lsFormatID(sys.GID)
default:
if fiExt, ok := dirent.(FileInfoUidGid); ok {
uid = lsFormatID(fiExt.Uid())
gid = lsFormatID(fiExt.Gid())
break
}
numLinks, uid, gid = lsLinksUIDGID(dirent)
}

View file

@ -1,3 +1,4 @@
//go:build plan9
// +build plan9
package sftp

View file

@ -1,3 +1,4 @@
//go:build windows || android
// +build windows android
package sftp

View file

@ -1,3 +1,4 @@
//go:build aix || darwin || dragonfly || freebsd || (!android && linux) || netbsd || openbsd || solaris || js
// +build aix darwin dragonfly freebsd !android,linux netbsd openbsd solaris js
package sftp

View file

@ -40,7 +40,7 @@ func newPktMgr(sender packetSender) *packetManager {
return s
}
//// packet ordering
// // packet ordering
func (s *packetManager) newOrderID() uint32 {
s.packetCount++
return s.packetCount
@ -89,7 +89,7 @@ func (o orderedPackets) Sort() {
})
}
//// packet registry
// // packet registry
// register incoming packets to be handled
func (s *packetManager) incomingPacket(pkt orderedRequest) {
s.working.Add(1)

View file

@ -31,7 +31,7 @@ type notReadOnly interface {
notReadOnly()
}
//// define types by adding methods
// // define types by adding methods
// hasPath
func (p *sshFxpLstatPacket) getPath() string { return p.Path }
func (p *sshFxpStatPacket) getPath() string { return p.Path }

25
vendor/github.com/pkg/sftp/packet.go generated vendored
View file

@ -71,6 +71,15 @@ func marshalFileInfo(b []byte, fi os.FileInfo) []byte {
b = marshalUint32(b, fileStat.Mtime)
}
if flags&sshFileXferAttrExtended != 0 {
b = marshalUint32(b, uint32(len(fileStat.Extended)))
for _, attr := range fileStat.Extended {
b = marshalString(b, attr.ExtType)
b = marshalString(b, attr.ExtData)
}
}
return b
}
@ -281,6 +290,11 @@ func recvPacket(r io.Reader, alloc *allocator, orderID uint32) (uint8, []byte, e
b = make([]byte, length)
}
if _, err := io.ReadFull(r, b[:length]); err != nil {
// ReadFull only returns EOF if it has read no bytes.
// In this case, that means a partial packet, and thus unexpected.
if err == io.EOF {
err = io.ErrUnexpectedEOF
}
debug("recv packet %d bytes: err %v", length, err)
return 0, nil, err
}
@ -522,7 +536,12 @@ func (p *sshFxpRmdirPacket) UnmarshalBinary(b []byte) error {
}
type sshFxpSymlinkPacket struct {
ID uint32
ID uint32
// The order of the arguments to the SSH_FXP_SYMLINK method was inadvertently reversed.
// Unfortunately, the reversal was not noticed until the server was widely deployed.
// Covered in Section 4.1 of https://github.com/openssh/openssh-portable/blob/master/PROTOCOL
Targetpath string
Linkpath string
}
@ -1242,7 +1261,7 @@ func (p *sshFxpExtendedPacketPosixRename) UnmarshalBinary(b []byte) error {
}
func (p *sshFxpExtendedPacketPosixRename) respond(s *Server) responsePacket {
err := os.Rename(toLocalPath(p.Oldpath), toLocalPath(p.Newpath))
err := os.Rename(s.toLocalPath(p.Oldpath), s.toLocalPath(p.Newpath))
return statusFromError(p.ID, err)
}
@ -1271,6 +1290,6 @@ func (p *sshFxpExtendedPacketHardlink) UnmarshalBinary(b []byte) error {
}
func (p *sshFxpExtendedPacketHardlink) respond(s *Server) responsePacket {
err := os.Link(toLocalPath(p.Oldpath), toLocalPath(p.Newpath))
err := os.Link(s.toLocalPath(p.Oldpath), s.toLocalPath(p.Newpath))
return statusFromError(p.ID, err)
}

View file

@ -1,3 +1,4 @@
//go:build !debug
// +build !debug
package sftp

View file

@ -391,21 +391,6 @@ func (fs *root) Filelist(r *Request) (ListerAt, error) {
return nil, err
}
return listerat{file}, nil
case "Readlink":
symlink, err := fs.readlink(r.Filepath)
if err != nil {
return nil, err
}
// SFTP-v2: The server will respond with a SSH_FXP_NAME packet containing only
// one name and a dummy attributes value.
return listerat{
&memFile{
name: symlink,
err: os.ErrNotExist, // prevent accidental use as a reader/writer.
},
}, nil
}
return nil, errors.New("unsupported")
@ -434,7 +419,7 @@ func (fs *root) readdir(pathname string) ([]os.FileInfo, error) {
return files, nil
}
func (fs *root) readlink(pathname string) (string, error) {
func (fs *root) Readlink(pathname string) (string, error) {
file, err := fs.lfetch(pathname)
if err != nil {
return "", err
@ -464,19 +449,10 @@ func (fs *root) Lstat(r *Request) (ListerAt, error) {
return listerat{file}, nil
}
// implements RealpathFileLister interface
func (fs *root) Realpath(p string) string {
if fs.startDirectory == "" || fs.startDirectory == "/" {
return cleanPath(p)
}
return cleanPathWithBase(fs.startDirectory, p)
}
// In memory file-system-y thing that the Hanlders live on
type root struct {
rootFile *memFile
mockErr error
startDirectory string
rootFile *memFile
mockErr error
mu sync.Mutex
files map[string]*memFile
@ -534,8 +510,8 @@ func (fs *root) exists(path string) bool {
return err != os.ErrNotExist
}
func (fs *root) fetch(path string) (*memFile, error) {
file, err := fs.lfetch(path)
func (fs *root) fetch(pathname string) (*memFile, error) {
file, err := fs.lfetch(pathname)
if err != nil {
return nil, err
}
@ -546,7 +522,12 @@ func (fs *root) fetch(path string) (*memFile, error) {
return nil, errTooManySymlinks
}
file, err = fs.lfetch(file.symlink)
linkTarget := file.symlink
if !path.IsAbs(linkTarget) {
linkTarget = path.Join(path.Dir(file.name), linkTarget)
}
file, err = fs.lfetch(linkTarget)
if err != nil {
return nil, err
}

View file

@ -74,6 +74,11 @@ type StatVFSFileCmder interface {
// FileLister should return an object that fulfils the ListerAt interface
// Note in cases of an error, the error text will be sent to the client.
// Called for Methods: List, Stat, Readlink
//
// Since Filelist returns an os.FileInfo, this can make it non-ideal for implementing Readlink.
// This is because the Name receiver method defined by that interface defines that it should only return the base name.
// However, Readlink is required to be capable of returning essentially any arbitrary valid path relative or absolute.
// In order to implement this more expressive requirement, implement [ReadlinkFileLister] which will then be used instead.
type FileLister interface {
Filelist(*Request) (ListerAt, error)
}
@ -87,12 +92,33 @@ type LstatFileLister interface {
}
// RealPathFileLister is a FileLister that implements the Realpath method.
// We use "/" as start directory for relative paths, implementing this
// interface you can customize the start directory.
// The built-in RealPath implementation does not resolve symbolic links.
// By implementing this interface you can customize the returned path
// and, for example, resolve symbolinc links if needed for your use case.
// You have to return an absolute POSIX path.
//
// Deprecated: if you want to set a start directory use WithStartDirectory RequestServerOption instead.
// Up to v1.13.5 the signature for the RealPath method was:
//
// # RealPath(string) string
//
// we have added a legacyRealPathFileLister that implements the old method
// to ensure that your code does not break.
// You should use the new method signature to avoid future issues
type RealPathFileLister interface {
FileLister
RealPath(string) (string, error)
}
// ReadlinkFileLister is a FileLister that implements the Readlink method.
// By implementing the Readlink method, it is possible to return any arbitrary valid path relative or absolute.
// This allows giving a better response than via the default FileLister (which is limited to os.FileInfo, whose Name method should only return the base name of a file)
type ReadlinkFileLister interface {
FileLister
Readlink(string) (string, error)
}
// This interface is here for backward compatibility only
type legacyRealPathFileLister interface {
FileLister
RealPath(string) string
}
@ -105,11 +131,19 @@ type NameLookupFileLister interface {
LookupGroupName(string) string
}
// ListerAt does for file lists what io.ReaderAt does for files.
// ListAt should return the number of entries copied and an io.EOF
// error if at end of list. This is testable by comparing how many you
// copied to how many could be copied (eg. n < len(ls) below).
// ListerAt does for file lists what io.ReaderAt does for files, i.e. a []os.FileInfo buffer is passed to the ListAt function
// and the entries that are populated in the buffer will be passed to the client.
//
// ListAt should return the number of entries copied and an io.EOF error if at end of list.
// This is testable by comparing how many you copied to how many could be copied (eg. n < len(ls) below).
// The copy() builtin is best for the copying.
//
// Uid and gid information will on unix systems be retrieved from [os.FileInfo.Sys]
// if this function returns a [syscall.Stat_t] when called on a populated entry.
// Alternatively, if the entry implements [FileInfoUidGid], it will be used for uid and gid information.
//
// If a populated entry implements [FileInfoExtendedData], extended attributes will also be returned to the client.
//
// Note in cases of an error, the error text will be sent to the client.
type ListerAt interface {
ListAt([]os.FileInfo, int64) (int, error)

View file

@ -1,10 +1,9 @@
//go:build plan9
// +build plan9
package sftp
import (
"path"
"path/filepath"
"syscall"
)
@ -15,20 +14,3 @@ func fakeFileInfoSys() interface{} {
func testOsSys(sys interface{}) error {
return nil
}
func toLocalPath(p string) string {
lp := filepath.FromSlash(p)
if path.IsAbs(p) {
tmp := lp[1:]
if filepath.IsAbs(tmp) {
// If the FromSlash without any starting slashes is absolute,
// then we have a filepath encoded with a prefix '/'.
// e.g. "/#s/boot" to "#s/boot"
return tmp
}
}
return lp
}

View file

@ -28,7 +28,7 @@ then sends to the client.
Handler for "Put" method and returns an io.Writer for the file which the server
then writes the uploaded file to. The file opening "pflags" are currently
preserved in the Request.Flags field as a 32bit bitmask value. See the [SFTP
spec](https://tools.ietf.org/html/draft-ietf-secsh-filexfer-02#section-6.3) for
spec](https://filezilla-project.org/specs/draft-ietf-secsh-filexfer-02.txt#section-6.3) for
details.
### Filecmd(*Request) error

View file

@ -219,12 +219,21 @@ func (rs *RequestServer) packetWorker(ctx context.Context, pktChan chan orderedR
rpkt = statusFromError(pkt.ID, rs.closeRequest(handle))
case *sshFxpRealpathPacket:
var realPath string
if realPather, ok := rs.Handlers.FileList.(RealPathFileLister); ok {
realPath = realPather.RealPath(pkt.getPath())
} else {
var err error
switch pather := rs.Handlers.FileList.(type) {
case RealPathFileLister:
realPath, err = pather.RealPath(pkt.getPath())
case legacyRealPathFileLister:
realPath = pather.RealPath(pkt.getPath())
default:
realPath = cleanPathWithBase(rs.startDirectory, pkt.getPath())
}
rpkt = cleanPacketPath(pkt, realPath)
if err != nil {
rpkt = statusFromError(pkt.ID, err)
} else {
rpkt = cleanPacketPath(pkt, realPath)
}
case *sshFxpOpendirPacket:
request := requestFromPacket(ctx, pkt, rs.startDirectory)
handle := rs.nextRequest(request)

View file

@ -1,3 +1,4 @@
//go:build !windows && !plan9
// +build !windows,!plan9
package sftp
@ -21,7 +22,3 @@ func testOsSys(sys interface{}) error {
}
return nil
}
func toLocalPath(p string) string {
return p
}

View file

@ -187,6 +187,7 @@ func requestFromPacket(ctx context.Context, pkt hasPath, baseDir string) *Reques
// NOTE: given a POSIX compliant signature: symlink(target, linkpath string)
// this makes Request.Target the linkpath, and Request.Filepath the target.
request.Target = cleanPathWithBase(baseDir, p.Linkpath)
request.Filepath = p.Targetpath
case *sshFxpExtendedPacketHardlink:
request.Target = cleanPathWithBase(baseDir, p.Newpath)
}
@ -294,7 +295,12 @@ func (r *Request) call(handlers Handlers, pkt requestPacket, alloc *allocator, o
return filecmd(handlers.FileCmd, r, pkt)
case "List":
return filelist(handlers.FileList, r, pkt)
case "Stat", "Lstat", "Readlink":
case "Stat", "Lstat":
return filestat(handlers.FileList, r, pkt)
case "Readlink":
if readlinkFileLister, ok := handlers.FileList.(ReadlinkFileLister); ok {
return readlink(readlinkFileLister, r, pkt)
}
return filestat(handlers.FileList, r, pkt)
default:
return statusFromError(pkt.id(), fmt.Errorf("unexpected method: %s", r.Method))
@ -598,6 +604,23 @@ func filestat(h FileLister, r *Request, pkt requestPacket) responsePacket {
}
}
func readlink(readlinkFileLister ReadlinkFileLister, r *Request, pkt requestPacket) responsePacket {
resolved, err := readlinkFileLister.Readlink(r.Filepath)
if err != nil {
return statusFromError(pkt.id(), err)
}
return &sshFxpNamePacket{
ID: pkt.id(),
NameAttrs: []*sshFxpNameAttr{
{
Name: resolved,
LongName: resolved,
Attrs: emptyFileStat,
},
},
}
}
// init attributes of request object from packet data
func requestMethod(p requestPacket) (method string) {
switch p.(type) {

View file

@ -1,8 +1,6 @@
package sftp
import (
"path"
"path/filepath"
"syscall"
)
@ -13,32 +11,3 @@ func fakeFileInfoSys() interface{} {
func testOsSys(sys interface{}) error {
return nil
}
func toLocalPath(p string) string {
lp := filepath.FromSlash(p)
if path.IsAbs(p) {
tmp := lp
for len(tmp) > 0 && tmp[0] == '\\' {
tmp = tmp[1:]
}
if filepath.IsAbs(tmp) {
// If the FromSlash without any starting slashes is absolute,
// then we have a filepath encoded with a prefix '/'.
// e.g. "/C:/Windows" to "C:\\Windows"
return tmp
}
tmp += "\\"
if filepath.IsAbs(tmp) {
// If the FromSlash without any starting slashes but with extra end slash is absolute,
// then we have a filepath encoded with a prefix '/' and a dropped '/' at the end.
// e.g. "/C:" to "C:\\"
return tmp
}
}
return lp
}

62
vendor/github.com/pkg/sftp/server.go generated vendored
View file

@ -24,7 +24,7 @@ const (
// Server is an SSH File Transfer Protocol (sftp) server.
// This is intended to provide the sftp subsystem to an ssh server daemon.
// This implementation currently supports most of sftp server protocol version 3,
// as specified at http://tools.ietf.org/html/draft-ietf-secsh-filexfer-02
// as specified at https://filezilla-project.org/specs/draft-ietf-secsh-filexfer-02.txt
type Server struct {
*serverConn
debugStream io.Writer
@ -33,6 +33,7 @@ type Server struct {
openFiles map[string]*os.File
openFilesLock sync.RWMutex
handleCount int
workDir string
}
func (svr *Server) nextHandle(f *os.File) string {
@ -128,6 +129,16 @@ func WithAllocator() ServerOption {
}
}
// WithServerWorkingDirectory sets a working directory to use as base
// for relative paths.
// If unset the default is current working directory (os.Getwd).
func WithServerWorkingDirectory(workDir string) ServerOption {
return func(s *Server) error {
s.workDir = cleanPath(workDir)
return nil
}
}
type rxPacket struct {
pktType fxp
pktBytes []byte
@ -174,7 +185,7 @@ func handlePacket(s *Server, p orderedRequest) error {
}
case *sshFxpStatPacket:
// stat the requested file
info, err := os.Stat(toLocalPath(p.Path))
info, err := os.Stat(s.toLocalPath(p.Path))
rpkt = &sshFxpStatResponse{
ID: p.ID,
info: info,
@ -184,7 +195,7 @@ func handlePacket(s *Server, p orderedRequest) error {
}
case *sshFxpLstatPacket:
// stat the requested file
info, err := os.Lstat(toLocalPath(p.Path))
info, err := os.Lstat(s.toLocalPath(p.Path))
rpkt = &sshFxpStatResponse{
ID: p.ID,
info: info,
@ -208,24 +219,24 @@ func handlePacket(s *Server, p orderedRequest) error {
}
case *sshFxpMkdirPacket:
// TODO FIXME: ignore flags field
err := os.Mkdir(toLocalPath(p.Path), 0755)
err := os.Mkdir(s.toLocalPath(p.Path), 0o755)
rpkt = statusFromError(p.ID, err)
case *sshFxpRmdirPacket:
err := os.Remove(toLocalPath(p.Path))
err := os.Remove(s.toLocalPath(p.Path))
rpkt = statusFromError(p.ID, err)
case *sshFxpRemovePacket:
err := os.Remove(toLocalPath(p.Filename))
err := os.Remove(s.toLocalPath(p.Filename))
rpkt = statusFromError(p.ID, err)
case *sshFxpRenamePacket:
err := os.Rename(toLocalPath(p.Oldpath), toLocalPath(p.Newpath))
err := os.Rename(s.toLocalPath(p.Oldpath), s.toLocalPath(p.Newpath))
rpkt = statusFromError(p.ID, err)
case *sshFxpSymlinkPacket:
err := os.Symlink(toLocalPath(p.Targetpath), toLocalPath(p.Linkpath))
err := os.Symlink(s.toLocalPath(p.Targetpath), s.toLocalPath(p.Linkpath))
rpkt = statusFromError(p.ID, err)
case *sshFxpClosePacket:
rpkt = statusFromError(p.ID, s.closeHandle(p.Handle))
case *sshFxpReadlinkPacket:
f, err := os.Readlink(toLocalPath(p.Path))
f, err := os.Readlink(s.toLocalPath(p.Path))
rpkt = &sshFxpNamePacket{
ID: p.ID,
NameAttrs: []*sshFxpNameAttr{
@ -240,7 +251,7 @@ func handlePacket(s *Server, p orderedRequest) error {
rpkt = statusFromError(p.ID, err)
}
case *sshFxpRealpathPacket:
f, err := filepath.Abs(toLocalPath(p.Path))
f, err := filepath.Abs(s.toLocalPath(p.Path))
f = cleanPath(f)
rpkt = &sshFxpNamePacket{
ID: p.ID,
@ -256,13 +267,14 @@ func handlePacket(s *Server, p orderedRequest) error {
rpkt = statusFromError(p.ID, err)
}
case *sshFxpOpendirPacket:
p.Path = toLocalPath(p.Path)
lp := s.toLocalPath(p.Path)
if stat, err := os.Stat(p.Path); err != nil {
if stat, err := os.Stat(lp); err != nil {
rpkt = statusFromError(p.ID, err)
} else if !stat.IsDir() {
rpkt = statusFromError(p.ID, &os.PathError{
Path: p.Path, Err: syscall.ENOTDIR})
Path: lp, Err: syscall.ENOTDIR,
})
} else {
rpkt = (&sshFxpOpenPacket{
ID: p.ID,
@ -315,7 +327,7 @@ func handlePacket(s *Server, p orderedRequest) error {
}
// Serve serves SFTP connections until the streams stop or the SFTP subsystem
// is stopped.
// is stopped. It returns nil if the server exits cleanly.
func (svr *Server) Serve() error {
defer func() {
if svr.pktMgr.alloc != nil {
@ -341,6 +353,10 @@ func (svr *Server) Serve() error {
for {
pktType, pktBytes, err = svr.serverConn.recvPacket(svr.pktMgr.getNextOrderID())
if err != nil {
// Check whether the connection terminated cleanly in-between packets.
if err == io.EOF {
err = nil
}
// we don't care about releasing allocated pages here, the server will quit and the allocator freed
break
}
@ -446,7 +462,7 @@ func (p *sshFxpOpenPacket) respond(svr *Server) responsePacket {
osFlags |= os.O_EXCL
}
f, err := os.OpenFile(toLocalPath(p.Path), osFlags, 0644)
f, err := os.OpenFile(svr.toLocalPath(p.Path), osFlags, 0o644)
if err != nil {
return statusFromError(p.ID, err)
}
@ -484,7 +500,7 @@ func (p *sshFxpSetstatPacket) respond(svr *Server) responsePacket {
b := p.Attrs.([]byte)
var err error
p.Path = toLocalPath(p.Path)
p.Path = svr.toLocalPath(p.Path)
debug("setstat name \"%s\"", p.Path)
if (p.Flags & sshFileXferAttrSize) != 0 {
@ -603,13 +619,15 @@ func statusFromError(id uint32, err error) *sshFxpStatusPacket {
return ret
}
switch e := err.(type) {
case fxerr:
if errors.Is(err, io.EOF) {
ret.StatusError.Code = sshFxEOF
return ret
}
var e fxerr
if errors.As(err, &e) {
ret.StatusError.Code = uint32(e)
default:
if e == io.EOF {
ret.StatusError.Code = sshFxEOF
}
return ret
}
return ret

27
vendor/github.com/pkg/sftp/server_plan9.go generated vendored Normal file
View file

@ -0,0 +1,27 @@
package sftp
import (
"path"
"path/filepath"
)
func (s *Server) toLocalPath(p string) string {
if s.workDir != "" && !path.IsAbs(p) {
p = path.Join(s.workDir, p)
}
lp := filepath.FromSlash(p)
if path.IsAbs(p) {
tmp := lp[1:]
if filepath.IsAbs(tmp) {
// If the FromSlash without any starting slashes is absolute,
// then we have a filepath encoded with a prefix '/'.
// e.g. "/#s/boot" to "#s/boot"
return tmp
}
}
return lp
}

View file

@ -1,3 +1,4 @@
//go:build darwin || linux
// +build darwin linux
// fill in statvfs structure with OS specific values

View file

@ -1,3 +1,4 @@
//go:build linux
// +build linux
package sftp

View file

@ -1,3 +1,4 @@
//go:build !darwin && !linux && !plan9
// +build !darwin,!linux,!plan9
package sftp

16
vendor/github.com/pkg/sftp/server_unix.go generated vendored Normal file
View file

@ -0,0 +1,16 @@
//go:build !windows && !plan9
// +build !windows,!plan9
package sftp
import (
"path"
)
func (s *Server) toLocalPath(p string) string {
if s.workDir != "" && !path.IsAbs(p) {
p = path.Join(s.workDir, p)
}
return p
}

39
vendor/github.com/pkg/sftp/server_windows.go generated vendored Normal file
View file

@ -0,0 +1,39 @@
package sftp
import (
"path"
"path/filepath"
)
func (s *Server) toLocalPath(p string) string {
if s.workDir != "" && !path.IsAbs(p) {
p = path.Join(s.workDir, p)
}
lp := filepath.FromSlash(p)
if path.IsAbs(p) {
tmp := lp
for len(tmp) > 0 && tmp[0] == '\\' {
tmp = tmp[1:]
}
if filepath.IsAbs(tmp) {
// If the FromSlash without any starting slashes is absolute,
// then we have a filepath encoded with a prefix '/'.
// e.g. "/C:/Windows" to "C:\\Windows"
return tmp
}
tmp += "\\"
if filepath.IsAbs(tmp) {
// If the FromSlash without any starting slashes but with extra end slash is absolute,
// then we have a filepath encoded with a prefix '/' and a dropped '/' at the end.
// e.g. "/C:" to "C:\\"
return tmp
}
}
return lp
}

2
vendor/github.com/pkg/sftp/sftp.go generated vendored
View file

@ -1,5 +1,5 @@
// Package sftp implements the SSH File Transfer Protocol as described in
// https://tools.ietf.org/html/draft-ietf-secsh-filexfer-02
// https://filezilla-project.org/specs/draft-ietf-secsh-filexfer-02.txt
package sftp
import (

View file

@ -1,3 +1,4 @@
//go:build plan9 || windows || (js && wasm)
// +build plan9 windows js,wasm
// Go defines S_IFMT on windows, plan9 and js/wasm as 0x1f000 instead of

View file

@ -1,4 +1,6 @@
// +build !plan9,!windows
//go:build !plan9 && !windows && (!js || !wasm)
// +build !plan9
// +build !windows
// +build !js !wasm
package sftp