sftp

package module
v0.0.0-...-1fc6486 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 17, 2026 License: BSD-3-Clause Imports: 4 Imported by: 0

README

go-ruby-net-sftp/net-sftp

net-sftp — go-ruby-net-sftp

Docs License Go Coverage

A pure-Go (no cgo) reimplementation of Ruby's Net::SFTP wire codec — the SFTP protocol (draft-ietf-secsh-filexfer) packet encode/decode for protocol versions 1 through 6. It frames FXP_* request packets, parses FXP_* response packets, serialises the version-aware file-attributes (ATTRS) structure, decodes directory Name entries, negotiates the protocol version, and correlates responses to requests by id — byte-for-byte identical to the net-sftp gem, with no Ruby runtime.

It is the SFTP backend for go-embedded-ruby, but is a standalone, reusable module — a sibling of go-ruby-net-smtp and go-ruby-net-imap.

What it is — and isn't. SFTP runs over an SSH channel. The packet protocol — framing, the ATTRS struct, request-id correlation, version negotiation — is fully deterministic and needs no interpreter and no real SSH, so it lives here as pure Go. The SSH transport itself (opening the channel, encryption, and reading/writing the framed bytes) is the host's job — the "channel seam": the host writes the bytes from FramePacket / InitPacket to the channel, and feeds bytes read back from the channel into a PacketParser. There is no go-ruby-ssh yet, so the encrypted transport is supplied by rbgo / the embedding host.

Features

Faithful port of Net::SFTP's protocol layer, validated against the net-sftp gem on every supported platform:

  • All packet types, versions 1–6FXP_INIT/VERSION, OPEN/CLOSE/READ/ WRITE, OPENDIR/READDIR, REMOVE/MKDIR/RMDIR/REALPATH, STAT/LSTAT/ FSTAT/SETSTAT/FSETSTAT, RENAME/READLINK/SYMLINK/LINK, BLOCK/ UNBLOCK, and the STATUS/HANDLE/DATA/NAME/ATTRS responses.
  • The version-aware ATTRS struct — the v1/2/3 layout (size/uid/gid/ permissions/atime/mtime/extended), the v4/5 layout (leading type byte, owner/group strings, subsecond times, ACLs), and the v6 layout (allocation_size, ctime, attrib_bits, text_hint, mime_type, link_count, untranslated_name).
  • Name entries — filename + attributes + the v1–3 longname, with an ls-style longname synthesised on demand for v4+.
  • Protocol-version negotiationFXP_INIT advertises the client's highest version; FXP_VERSION is parsed (with its extension pairs) and the negotiated version is min(server, client).
  • Request-id correlation — ids allocated monotonically from 0 (matching MRI); every response echoes its request id so the host can match it.
  • Status handlingFXP_STATUS code → StatusException, with the canonical description map (Net::SFTP::Response::MAP).

CGO-free, dependency-free, 100% test coverage, gofmt + go vet clean, and green across the six 64-bit Go targets (amd64, arm64, riscv64, loong64, ppc64le, s390x) — the big-endian s390x lane included, since the codec is explicitly network-byte-order.

Install

go get github.com/go-ruby-net-sftp/net-sftp

Usage

The library builds the bytes you write to (and parses the bytes you read from) an SSH channel. The transport is yours:

package main

import (
	"fmt"

	sftp "github.com/go-ruby-net-sftp/net-sftp"
)

func main() {
	// 1. Open: advertise our highest version, then negotiate from the server's reply.
	channelWrite(sftp.InitPacket(sftp.HighestProtocolVersionSupported))

	parser := sftp.NewPacketParser()
	parser.Feed(channelRead())
	pkt, _ := parser.Next() // the FXP_VERSION packet
	info, _ := sftp.ParseVersion(pkt.Payload)
	version, _ := sftp.NegotiateVersion(info.Version, sftp.HighestProtocolVersionSupported)

	p := sftp.NewProtocol(version)

	// 2. Request: open a file for reading. The frame goes straight to the channel.
	id, frame := p.Open("/etc/hostname", sftp.FV1_READ, 0, nil)
	channelWrite(frame)

	// 3. Response: feed channel bytes in, correlate by id, decode by type.
	parser.Feed(channelRead())
	resp, _ := parser.Next()
	switch v := mustParse(sftp.ParseResponse(resp, version)).(type) {
	case *sftp.HandleResponse:
		if v.ID == id {
			fmt.Printf("handle = %x\n", v.Handle)
		}
	case *sftp.StatusResponse:
		fmt.Println(sftp.NewStatusException(v, "open"))
	}
}

channelRead / channelWrite are the host's SSH-channel seam — this package never touches the network.

The SSH-channel seam

host (SSH transport)                this library (SFTP packet codec)
────────────────────                ────────────────────────────────
open SSH session + channel
                          <──────── InitPacket / Protocol.<Request>  → framed bytes
channel.write(frame) ─────┘
channel.read()  ──────────┐
                          └───────► PacketParser.Feed → .Next → Packet
                                    ParseResponse / ParseVersion → typed value

The host supplies the encrypted byte transport; this library owns everything from the packet length prefix inward.

API

// Framing & transport seam.
func FramePacket(typ byte, payload []byte) []byte
func ParsePacket(frame []byte) (Packet, error)
type PacketParser struct{ /* … */ }
func NewPacketParser() *PacketParser
func (*PacketParser) Feed(b []byte)
func (*PacketParser) Next() (Packet, bool)

// Version negotiation.
func InitPacket(version int) []byte
func ParseVersion(payload []byte) (VersionInfo, error)
func NegotiateVersion(serverVersion uint32, clientHighest int) (int, error)

// Request builders (each returns the request id + framed bytes).
type Protocol struct{ /* … */ }
func NewProtocol(version int) *Protocol
func (*Protocol) Open(path string, sftpFlags, desiredAccess uint32, attrs *Attributes) (uint32, []byte)
func (*Protocol) Read(handle []byte, offset uint64, length uint32) (uint32, []byte)
func (*Protocol) Write(handle []byte, offset uint64, data []byte) (uint32, []byte)
// … Close, Stat, Lstat, Fstat, Setstat, Fsetstat, Opendir, Readdir, Remove,
//    Mkdir, Rmdir, Realpath, Rename, Readlink, Symlink, Link, Block, Unblock.

// Response parsers (each returns the echoed request id + decoded payload).
func ParseStatus(payload []byte, version int) (*StatusResponse, error)
func ParseHandle(payload []byte) (*HandleResponse, error)
func ParseData(payload []byte) (*DataResponse, error)
func ParseName(payload []byte, version int) (*NameResponse, error)
func ParseAttrs(payload []byte, version int) (*AttrsResponse, error)
func ParseResponse(pkt Packet, version int) (any, error)

// The version-aware attributes structure.
type Attributes struct{ /* optional fields are pointers */ }
func (*Attributes) Encode(version int) []byte
func DecodeAttributes(r *Reader, version int) (*Attributes, error)

// Status → exception.
type StatusException struct{ Code uint32; Description, Text string }

Tests & coverage

The suite pairs deterministic, ruby-free tests (which alone hold coverage at 100%, so the qemu cross-arch and Windows lanes pass the gate) with a differential MRI oracle: every request builder, the FXP_INIT frame, and the v1/v4/v6 ATTRS struct are emitted here and compared byte-for-byte against the real net-sftp gem driving its own protocol classes; gem-built response packets are decoded here. The oracle scripts $stdout.binmode so Windows text-mode never pollutes the bytes, and skip themselves where ruby or the gem is absent.

COVERPKG=$(go list ./... | paste -sd, -)
go test -race -coverpkg="$COVERPKG" -coverprofile=cover.out ./...
go tool cover -func=cover.out | tail -1   # 100.0%

License

BSD-3-Clause — see LICENSE. Copyright the go-ruby-net-sftp/net-sftp authors.

Documentation

Overview

Package sftp is a pure-Go (CGO=0), MRI-faithful reimplementation of the wire codec behind Ruby's Net::SFTP — the SFTP protocol (draft-ietf-secsh-filexfer) packet encode/decode for versions 1 through 6.

SFTP runs over an SSH channel. This package owns only the deterministic, interpreter-independent part: framing FXP_* request packets, parsing FXP_* response packets, the version-aware file-attributes (ATTRS) structure, Name entries, request-id correlation, and protocol-version negotiation. The SSH transport itself — opening the channel, encryption, and the channel read/write of the framed bytes — is the host's responsibility (the "channel seam"): the host writes the bytes from Packet.Frame to the channel, and feeds bytes read from the channel into a PacketParser. No Ruby runtime and no real SSH are required to use, or to fully test, this library.

Index

Constants

View Source
const (
	FSize        = 0x00000001
	FUIDGID      = 0x00000002 // v1-v3 only
	FPermissions = 0x00000004
	FACModTime   = 0x00000008 // v1-v3 only (atime+mtime as 32-bit)

	// v4+ flags
	FAccessTime    = 0x00000008
	FCreateTime    = 0x00000010
	FModifyTime    = 0x00000020
	FACL           = 0x00000040
	FOwnerGroup    = 0x00000080
	FSubsecondTime = 0x00000100

	// v6+ flags
	FBits             = 0x00000200
	FAllocationSize   = 0x00000400
	FTextHint         = 0x00000800
	FMimeType         = 0x00001000
	FLinkCount        = 0x00002000
	FUntranslatedName = 0x00004000
	FCTime            = 0x00008000

	FExtended = 0x80000000
)

Attribute-structure presence flags. The version-1 layout (also used by v2/v3) uses F_SIZE/F_UIDGID/F_PERMISSIONS/F_ACMODTIME/F_EXTENDED; versions 4+ reuse the low bits for richer fields. These mirror the F_* constants on each Net::SFTP::Protocol::V0x::Attributes class.

View Source
const (
	FXP_INIT     = 1
	FXP_VERSION  = 2
	FXP_OPEN     = 3
	FXP_CLOSE    = 4
	FXP_READ     = 5
	FXP_WRITE    = 6
	FXP_LSTAT    = 7
	FXP_FSTAT    = 8
	FXP_SETSTAT  = 9
	FXP_FSETSTAT = 10
	FXP_OPENDIR  = 11
	FXP_READDIR  = 12
	FXP_REMOVE   = 13
	FXP_MKDIR    = 14
	FXP_RMDIR    = 15
	FXP_REALPATH = 16
	FXP_STAT     = 17
	FXP_RENAME   = 18
	FXP_READLINK = 19
	FXP_SYMLINK  = 20
	FXP_LINK     = 21
	FXP_BLOCK    = 22
	FXP_UNBLOCK  = 23

	FXP_STATUS = 101
	FXP_HANDLE = 102
	FXP_DATA   = 103
	FXP_NAME   = 104
	FXP_ATTRS  = 105

	FXP_EXTENDED       = 200
	FXP_EXTENDED_REPLY = 201
)

Packet type bytes for SFTP protocol versions 1 through 6 (Net::SFTP::Constants::PacketTypes).

View Source
const (
	RenameOverwrite = 0x00000001
	RenameAtomic    = 0x00000002
	RenameNative    = 0x00000004
)

FXP_RENAME flags, valid from protocol version 5 (Net::SFTP::Constants::RenameFlags).

View Source
const (
	FX_OK                     = 0
	FX_EOF                    = 1
	FX_NO_SUCH_FILE           = 2
	FX_PERMISSION_DENIED      = 3
	FX_FAILURE                = 4
	FX_BAD_MESSAGE            = 5
	FX_NO_CONNECTION          = 6
	FX_CONNECTION_LOST        = 7
	FX_OP_UNSUPPORTED         = 8
	FX_INVALID_HANDLE         = 9
	FX_NO_SUCH_PATH           = 10
	FX_FILE_ALREADY_EXISTS    = 11
	FX_WRITE_PROTECT          = 12
	FX_NO_MEDIA               = 13
	FX_NO_SPACE_ON_FILESYSTEM = 14
	FX_QUOTA_EXCEEDED         = 15
	FX_UNKNOWN_PRINCIPLE      = 16
	FX_LOCK_CONFLICT          = 17
	FX_DIR_NOT_EMPTY          = 18
	FX_NOT_A_DIRECTORY        = 19
	FX_INVALID_FILENAME       = 20
	FX_LINK_LOOP              = 21
)

FXP_STATUS codes (Net::SFTP::Constants::StatusCodes). FX_OK..FX_LINK_LOOP map to the human-readable descriptions returned by StatusDescription.

View Source
const (
	FV1_READ   = 0x00000001
	FV1_WRITE  = 0x00000002
	FV1_APPEND = 0x00000004
	FV1_CREAT  = 0x00000008
	FV1_TRUNC  = 0x00000010
	FV1_EXCL   = 0x00000020
)

Open-mode flags understood by protocol versions 1-4 (Net::SFTP::Constants::OpenFlags::FV1).

View Source
const (
	FV5_CREATE_NEW         = 0x00000000
	FV5_CREATE_TRUNCATE    = 0x00000001
	FV5_OPEN_EXISTING      = 0x00000002
	FV5_OPEN_OR_CREATE     = 0x00000003
	FV5_TRUNCATE_EXISTING  = 0x00000004
	FV5_APPEND_DATA        = 0x00000008
	FV5_APPEND_DATA_ATOMIC = 0x00000010
	FV5_TEXT_MODE          = 0x00000020
	FV5_READ_LOCK          = 0x00000040
	FV5_WRITE_LOCK         = 0x00000080
	FV5_DELETE_LOCK        = 0x00000100

	FV6_ADVISORY_LOCK           = 0x00000200
	FV6_NOFOLLOW                = 0x00000400
	FV6_DELETE_ON_CLOSE         = 0x00000800
	FV6_ACCESS_AUDIT_ALARM_INFO = 0x00001000
	FV6_ACCESS_BACKUP           = 0x00002000
	FV6_BACKUP_STREAM           = 0x00004000
	FV6_OVERRIDE_OWNER          = 0x00008000
)

Open-mode flags understood by protocol versions 5 and 6 (Net::SFTP::Constants::OpenFlags::FV5 / FV6).

View Source
const (
	LockRead     = FV5_READ_LOCK
	LockWrite    = FV5_WRITE_LOCK
	LockDelete   = FV5_DELETE_LOCK
	LockAdvisory = FV6_ADVISORY_LOCK
)

Byte-range lock types for FXP_BLOCK, protocol version 6 (Net::SFTP::Constants::LockTypes).

View Source
const (
	ACEAccessAllowed = 0x00000000
	ACEAccessDenied  = 0x00000001
	ACESystemAudit   = 0x00000002
	ACESystemAlarm   = 0x00000003
)

Access-control entry types, from protocol version 4 (Net::SFTP::Constants::ACE::Type).

View Source
const (
	ACEFileInherit        = 0x00000001
	ACEDirectoryInherit   = 0x00000002
	ACENoPropagateInherit = 0x00000004
	ACEInheritOnly        = 0x00000008
	ACESuccessfulAccess   = 0x00000010
	ACEFailedAccess       = 0x00000020
	ACEIdentifierGroup    = 0x00000040
)

Access-control entry flags, from protocol version 4 (Net::SFTP::Constants::ACE::Flag).

View Source
const (
	ACEReadData        = 0x00000001
	ACEListDirectory   = 0x00000001
	ACEWriteData       = 0x00000002
	ACEAddFile         = 0x00000002
	ACEAppendData      = 0x00000004
	ACEAddSubdirectory = 0x00000004
	ACEReadNamedAttrs  = 0x00000008
	ACEWriteNamedAttrs = 0x00000010
	ACEExecute         = 0x00000020
	ACEDeleteChild     = 0x00000040
	ACEReadAttributes  = 0x00000080
	ACEWriteAttributes = 0x00000100
	ACEDelete          = 0x00010000
	ACEReadACL         = 0x00020000
	ACEWriteACL        = 0x00040000
	ACEWriteOwner      = 0x00080000
	ACESynchronize     = 0x00100000
)

Access-control entry masks, from protocol version 4 (Net::SFTP::Constants::ACE::Mask).

View Source
const (
	TRegular     = 1
	TDirectory   = 2
	TSymlink     = 3
	TSpecial     = 4
	TUnknown     = 5
	TSocket      = 6
	TCharDevice  = 7
	TBlockDevice = 8
	TFIFO        = 9
)

File-type codes inferred from the permission bits of an Attributes value (Net::SFTP::Protocol::V01::Attributes T_* constants).

View Source
const (
	IORDONLY = 0x0000
	IOWRONLY = 0x0001
	IORDWR   = 0x0002
	IOAPPEND = 0x0008
	IOCREAT  = 0x0200
	IOTRUNC  = 0x0400
	IOEXCL   = 0x0800
)

IO-mode bits, matching Ruby's File::Constants used by Net::SFTP's open-flag translation. These are the conventional POSIX values MRI exposes via IO::*.

DefaultStatFlags is the flag mask used for stat/lstat/fstat requests in protocol versions 4 and above when no explicit flags are given (Net::SFTP::Protocol::V04::Base::DEFAULT_FLAGS).

View Source
const HighestProtocolVersionSupported = 6

HighestProtocolVersionSupported is the newest SFTP protocol version this codec implements (Net::SFTP::Session::HIGHEST_PROTOCOL_VERSION_SUPPORTED).

Variables

View Source
var ErrShortBuffer = errors.New("sftp: buffer underrun")

ErrShortBuffer is returned by a Reader when a read runs past the end of the underlying bytes. MRI's Net::SSH::Buffer returns nil in that case and the SFTP layer then raises; here the error is surfaced explicitly so callers can decide.

Functions

func FramePacket

func FramePacket(typ byte, payload []byte) []byte

FramePacket builds the on-the-wire bytes for a packet: a uint32 length prefix (payload length + 1 for the type byte), the type byte, then the payload. This is the framing Net::SFTP::Session#send_packet performs before handing the bytes to the SSH channel — the host writes the returned slice to the channel.

func InitPacket

func InitPacket(version int) []byte

InitPacket builds an FXP_INIT frame advertising the client's highest supported protocol version (Net::SFTP::Session#do_init). The returned bytes are written to the SSH channel by the host to begin the session.

func NegotiateVersion

func NegotiateVersion(serverVersion uint32, clientHighest int) (int, error)

NegotiateVersion returns the protocol version both peers agree on: the lesser of the server's version and the client's highest supported version (Net::SFTP::Session#do_version). It errors if the negotiated version is below 1.

func NormalizeOpenFlags

func NormalizeOpenFlags(mode string) (int, error)

NormalizeOpenFlags converts a mode string ("r", "r+", "w", "w+", "a", "a+", optionally with a "b") into the IO-bit combination Net::SFTP uses (Protocol::V01::Base#normalize_open_flags). It errors on an unsupported mode.

func OpenFlagsV1

func OpenFlagsV1(flags int) uint32

OpenFlagsV1 translates an IO-bit combination into the FV1 SFTP open-flag word used by protocol versions 1-4 (Protocol::V01::Base#open).

func OpenFlagsV5

func OpenFlagsV5(flags int) (sftpFlags, desiredAccess uint32)

OpenFlagsV5 translates an IO-bit combination into the (sftpFlags, desiredAccess) pair used by protocol versions 5 and 6 (Protocol::V05::Base#open). sftpFlags is one of the FV5_* dispositions (optionally OR'd with APPEND_DATA); desiredAccess is the ACE access mask.

func ParseResponse

func ParseResponse(pkt Packet, version int) (any, error)

ParseResponse decodes a response packet by its type byte, returning a typed value (*StatusResponse, *HandleResponse, *DataResponse, *NameResponse, or *AttrsResponse). It errors on a request (non-response) or unknown type, matching Protocol::Base#parse.

func ResponseID

func ResponseID(payload []byte) (uint32, error)

ResponseID reads just the request id (the first field) from any response payload, so a host can correlate a packet to its request before dispatching to the type-specific parser.

func StatusDescription

func StatusDescription(code uint32) string

StatusDescription returns the human-readable name for a status code, matching Net::SFTP::Response::MAP. Unknown codes return "" (as MAP[code] would be nil).

Types

type ACL

type ACL struct {
	Type uint32
	Flag uint32
	Mask uint32
	Who  string
}

ACL is one entry of an access-control list (v4+), Net::SFTP::Protocol::V04::Attributes::ACL.

type Attributes

type Attributes struct {
	Type *uint8 // v4+ leading type byte

	Size             *uint64
	AllocationSize   *uint64 // v6+
	UID              *uint32 // v1-v3
	GID              *uint32 // v1-v3
	Owner            *string // v4+
	Group            *string // v4+
	Permissions      *uint32
	Atime            *uint64 // 32-bit on v1-v3, 64-bit on v4+
	AtimeNanos       *uint32 // v4+ subsecond
	CreateTime       *uint64 // v4+
	CreateTimeNanos  *uint32 // v4+ subsecond
	Mtime            *uint64 // 32-bit on v1-v3, 64-bit on v4+
	MtimeNanos       *uint32 // v4+ subsecond
	CTime            *uint64 // v6+
	CTimeNanos       *uint32 // v6+ subsecond
	ACL              []ACL   // v4+
	AttribBits       *uint32 // v6+
	AttribBitsValid  *uint32 // v6+
	TextHint         *uint8  // v6+
	MimeType         *string // v6+
	LinkCount        *uint32 // v6+
	UntranslatedName *string // v6+
	Extended         []ExtPair
}

Attributes is the version-aware file-attributes (ATTRS) structure. Optional fields are pointers: a nil pointer means the corresponding presence bit is clear, matching MRI where an absent key leaves the flag unset. Encode and Decode are parameterised by protocol version so the same struct serialises into the v1, v4/v5, or v6 element layout.

func DecodeAttributes

func DecodeAttributes(r *Reader, version int) (*Attributes, error)

DecodeAttributes parses an attribute structure for the given protocol version from r, mirroring Attributes.from_buffer. Fields whose presence bit is clear are left nil.

func (*Attributes) Encode

func (a *Attributes) Encode(version int) []byte

Encode serialises the attributes for the given protocol version. It first computes the flags word from which fields are present, writes it, then writes each present element in layout order. The result matches Attributes#to_s.

func (*Attributes) FileType

func (a *Attributes) FileType() int

FileType classifies the attributes from their permission bits, returning one of the T_* constants (Net::SFTP::Protocol::V01::Attributes#type). It returns TUnknown when no permissions are set.

func (*Attributes) IsDirectory

func (a *Attributes) IsDirectory() (val, known bool)

IsDirectory reports whether the attributes describe a directory; the second return is false when the type is indeterminate (TUnknown), mirroring MRI's nil.

func (*Attributes) IsFile

func (a *Attributes) IsFile() (val, known bool)

IsFile reports whether the attributes describe a regular file (see IsDirectory).

func (a *Attributes) IsSymlink() (val, known bool)

IsSymlink reports whether the attributes describe a symlink (see IsDirectory).

type AttrsResponse

type AttrsResponse struct {
	ID    uint32
	Attrs *Attributes
}

AttrsResponse is a parsed FXP_ATTRS packet wrapping a single attribute structure.

func ParseAttrs

func ParseAttrs(payload []byte, version int) (*AttrsResponse, error)

ParseAttrs decodes an FXP_ATTRS payload for the given protocol version (Net::SFTP::Protocol::V01::Base#parse_attrs_packet).

type DataResponse

type DataResponse struct {
	ID   uint32
	Data []byte
}

DataResponse is a parsed FXP_DATA packet carrying file data read from the server.

func ParseData

func ParseData(payload []byte) (*DataResponse, error)

ParseData decodes an FXP_DATA payload (Net::SFTP::Protocol::V01::Base#parse_data_packet).

type ExtPair

type ExtPair struct {
	Name  string
	Value string
}

ExtPair is one name/value pair of the F_EXTENDED extended-attribute map. A slice preserves order so encode/decode round-trips byte-for-byte.

type HandleResponse

type HandleResponse struct {
	ID     uint32
	Handle []byte
}

HandleResponse is a parsed FXP_HANDLE packet wrapping an opaque file or directory handle.

func ParseHandle

func ParseHandle(payload []byte) (*HandleResponse, error)

ParseHandle decodes an FXP_HANDLE payload (Net::SFTP::Protocol::V01::Base#parse_handle_packet).

type Name

type Name struct {
	Filename string
	// Longname is the server-supplied display string (v1-v3). For v4+ it is empty
	// on the wire; call LongnameFor to render an ls-style line from the attributes.
	Longname   string
	Attributes *Attributes
}

Name is one entry of an FXP_NAME response: a remote filename, its attributes, and (in protocol versions 1-3) a server-supplied "longname" display string. From version 4 onward the wire format drops the longname field, and Longname is synthesised on demand from the attributes (Net::SFTP::Protocol::V04::Name).

func (*Name) IsDirectory

func (n *Name) IsDirectory() (val, known bool)

IsDirectory reports whether the entry is a directory (see Attributes.IsDirectory).

func (*Name) IsFile

func (n *Name) IsFile() (val, known bool)

IsFile reports whether the entry is a regular file.

func (n *Name) IsSymlink() (val, known bool)

IsSymlink reports whether the entry is a symlink.

func (*Name) LongnameFor

func (n *Name) LongnameFor(loc *time.Location) string

LongnameFor renders an ls-style display line for a v4+ Name, matching Net::SFTP::Protocol::V04::Name#longname. The mtime is formatted in loc (use time.Local to match MRI's Time.at).

type NameResponse

type NameResponse struct {
	ID    uint32
	Names []Name
}

NameResponse is a parsed FXP_NAME packet: the list of directory entries returned by FXP_READDIR / FXP_REALPATH.

func ParseName

func ParseName(payload []byte, version int) (*NameResponse, error)

ParseName decodes an FXP_NAME payload for the given protocol version. In v1-3 each entry carries filename, longname, and attributes; from v4 the longname field was dropped (Net::SFTP::Protocol::V04::Base#parse_name_packet).

type Packet

type Packet struct {
	Type    byte
	Payload []byte
}

Packet is a decoded SFTP packet: a type byte and the raw payload that follows it (the bytes after the length prefix and type, Net::SFTP::Packet). Use FramePacket to build the on-the-wire bytes and ParsePacket to split a framed packet back into type + payload.

func ParsePacket

func ParsePacket(frame []byte) (Packet, error)

ParsePacket splits a single framed packet (length prefix + type + payload) into its type and payload. It is the inverse of FramePacket and reports an error if the buffer is shorter than the declared length.

type PacketParser

type PacketParser struct {
	// contains filtered or unexported fields
}

PacketParser reassembles whole SFTP packets from a stream of channel bytes, mirroring Net::SFTP::Session#when_channel_polled. The host feeds it the bytes read from the SSH channel (in any chunking) via Feed; Next then yields each complete packet as it becomes available. This decouples the codec from the transport: no SSH, just the byte boundary logic.

func NewPacketParser

func NewPacketParser() *PacketParser

NewPacketParser returns an empty parser.

func (*PacketParser) Feed

func (p *PacketParser) Feed(b []byte)

Feed appends channel bytes to the parser's input buffer.

func (*PacketParser) Next

func (p *PacketParser) Next() (pkt Packet, ok bool)

Next returns the next complete packet, or ok=false if more bytes are needed. Call it in a loop after each Feed until it reports ok=false.

type Protocol

type Protocol struct {
	// contains filtered or unexported fields
}

Protocol is a version-specific SFTP request builder and response parser, mirroring the Net::SFTP::Protocol::V0x::Base hierarchy. It allocates request ids monotonically from -1 (so the first id is 0, exactly as MRI does) and frames each request as on-the-wire bytes the host writes to the SSH channel.

Every request method returns the request id and the framed packet bytes; the id lets the host correlate the eventual FXP_STATUS / FXP_HANDLE / … response back to the request (responses echo the id as their first field).

func NewProtocol

func NewProtocol(version int) *Protocol

NewProtocol returns a request builder for the negotiated protocol version (1-6). It panics for an out-of-range version, matching Protocol.load's raise.

func (*Protocol) Block

func (p *Protocol) Block(handle []byte, offset, length uint64, mask uint32) (uint32, []byte, error)

Block builds an FXP_BLOCK byte-range-lock request (v6 only). mask is a combination of the Lock* constants.

func (*Protocol) Close

func (p *Protocol) Close(handle []byte) (uint32, []byte)

Close builds an FXP_CLOSE request for a handle.

func (*Protocol) Fsetstat

func (p *Protocol) Fsetstat(handle []byte, attrs *Attributes) (uint32, []byte)

Fsetstat builds an FXP_FSETSTAT request, applying attrs to an open handle.

func (*Protocol) Fstat

func (p *Protocol) Fstat(handle []byte, flags *uint32) (uint32, []byte)

Fstat builds an FXP_FSTAT request for an open handle. flags is honoured from v4.

func (p *Protocol) Link(newLinkPath, existingPath string, symlink bool) (uint32, []byte, error)

Link builds an FXP_LINK request (v6 only): create newLinkPath pointing at existingPath; symlink selects a symbolic (true) or hard (false) link.

func (*Protocol) Lstat

func (p *Protocol) Lstat(path string, flags *uint32) (uint32, []byte)

Lstat builds an FXP_LSTAT request (does not follow symlinks).

func (*Protocol) Mkdir

func (p *Protocol) Mkdir(path string, attrs *Attributes) (uint32, []byte)

Mkdir builds an FXP_MKDIR request with the given directory attributes.

func (*Protocol) Open

func (p *Protocol) Open(path string, sftpFlags, desiredAccess uint32, attrs *Attributes) (uint32, []byte)

Open builds an FXP_OPEN request. sftpFlags is the protocol-specific open flag word (an FV1_* combination for v1-4, an FV5_* combination for v5-6); for v5+, desiredAccess is the ACE access mask that precedes the flags on the wire. attrs may be nil (an empty attribute structure is sent). Callers translate IO-style flags into sftpFlags / desiredAccess; OpenFlagsV1 and OpenFlagsV5 help.

func (*Protocol) Opendir

func (p *Protocol) Opendir(path string) (uint32, []byte)

Opendir builds an FXP_OPENDIR request.

func (*Protocol) Read

func (p *Protocol) Read(handle []byte, offset uint64, length uint32) (uint32, []byte)

Read builds an FXP_READ request: read length bytes at offset from handle.

func (*Protocol) Readdir

func (p *Protocol) Readdir(handle []byte) (uint32, []byte)

Readdir builds an FXP_READDIR request for an open directory handle.

func (p *Protocol) Readlink(path string) (uint32, []byte, error)

Readlink builds an FXP_READLINK request. Unavailable before v3.

func (*Protocol) Realpath

func (p *Protocol) Realpath(path string) (uint32, []byte)

Realpath builds an FXP_REALPATH request to canonicalise a path.

func (*Protocol) Remove

func (p *Protocol) Remove(filename string) (uint32, []byte)

Remove builds an FXP_REMOVE request to delete a file.

func (*Protocol) Rename

func (p *Protocol) Rename(name, newName string, flags *uint32) (uint32, []byte, error)

Rename builds an FXP_RENAME request. It is unavailable in v1 (returns an error). In v2-4 the flags word is omitted; in v5+ a flags word is appended (0 when nil).

func (*Protocol) Rmdir

func (p *Protocol) Rmdir(path string) (uint32, []byte)

Rmdir builds an FXP_RMDIR request.

func (*Protocol) Setstat

func (p *Protocol) Setstat(path string, attrs *Attributes) (uint32, []byte)

Setstat builds an FXP_SETSTAT request, applying attrs to the file at path.

func (*Protocol) Stat

func (p *Protocol) Stat(path string, flags *uint32) (uint32, []byte)

Stat builds an FXP_STAT request (follows symlinks). flags is honoured from v4.

func (p *Protocol) Symlink(path, target string) (uint32, []byte, error)

Symlink builds a symlink request. Unavailable before v3. In v3-5 it emits an FXP_SYMLINK packet; in v6 the older packet was removed, so it is expressed as an FXP_LINK with the symbolic flag set (Net::SFTP::Protocol::V06::Base#symlink).

func (*Protocol) Unblock

func (p *Protocol) Unblock(handle []byte, offset, length uint64) (uint32, []byte, error)

Unblock builds an FXP_UNBLOCK request releasing a byte-range lock (v6 only).

func (*Protocol) Version

func (p *Protocol) Version() int

Version reports the protocol version this builder targets.

func (*Protocol) Write

func (p *Protocol) Write(handle []byte, offset uint64, data []byte) (uint32, []byte)

Write builds an FXP_WRITE request: write data at offset to handle.

type Reader

type Reader struct {
	// contains filtered or unexported fields
}

Reader consumes SSH2-encoded values from a byte slice, mirroring Net::SSH::Buffer's read_* methods.

func NewReader

func NewReader(b []byte) *Reader

NewReader returns a Reader over b. The slice is referenced, not copied.

func (*Reader) EOF

func (r *Reader) EOF() bool

EOF reports whether the read position is at the end of the buffer (Net::SSH::Buffer#eof?).

func (*Reader) ReadBool

func (r *Reader) ReadBool() (bool, error)

ReadBool consumes one byte and reports whether it is non-zero (#read_bool, 'C' rules).

func (*Reader) ReadByte

func (r *Reader) ReadByte() (byte, error)

ReadByte consumes and returns the next byte (#read_byte).

func (*Reader) ReadString

func (r *Reader) ReadString() ([]byte, error)

ReadString consumes a uint32-length-prefixed byte string and returns its bytes (#read_string).

func (*Reader) ReadStringStr

func (r *Reader) ReadStringStr() (string, error)

ReadStringStr is ReadString returning a Go string.

func (*Reader) ReadUint32

func (r *Reader) ReadUint32() (uint32, error)

ReadUint32 consumes a 32-bit network-byte-order integer (#read_long).

func (*Reader) ReadUint64

func (r *Reader) ReadUint64() (uint64, error)

ReadUint64 consumes a 64-bit network-byte-order integer (#read_int64).

func (*Reader) Remaining

func (r *Reader) Remaining() int

Remaining reports how many unread bytes are left.

type StatusException

type StatusException struct {
	Code        uint32
	Description string
	Text        string
}

StatusException reports a non-success FXP_STATUS result, mirroring Net::SFTP::StatusException. Code is the FX_ status code, Description the human-readable name (the server-supplied message when present, else the canonical StatusDescription), and Text any incident-specific context.

func NewStatusException

func NewStatusException(s *StatusResponse, text string) *StatusException

NewStatusException builds an exception from an FXP_STATUS response. If the server message is empty, the canonical description for the code is used, as StatusException#initialize does.

func (*StatusException) Error

func (e *StatusException) Error() string

Error formats the exception as MRI's StatusException#message does: `<text> (<code>, "<description>")`, with the leading text omitted when blank.

type StatusResponse

type StatusResponse struct {
	ID       uint32
	Code     uint32
	Message  string
	Language string
}

StatusResponse is a parsed FXP_STATUS packet. Code is the FX_ status code; from protocol version 3, an error message and language tag follow on the wire.

func ParseStatus

func ParseStatus(payload []byte, version int) (*StatusResponse, error)

ParseStatus decodes an FXP_STATUS payload for the given protocol version. The message and language fields are present from version 3 onward; for v1/v2 only the code is read (Net::SFTP::Protocol::V01::Base#parse_status_packet).

func (*StatusResponse) EOF

func (s *StatusResponse) EOF() bool

EOF reports whether the status is FX_EOF (Net::SFTP::Response#eof?).

func (*StatusResponse) OK

func (s *StatusResponse) OK() bool

OK reports whether the status is FX_OK (Net::SFTP::Response#ok?).

type VersionInfo

type VersionInfo struct {
	Version    uint32
	Extensions []ExtPair
}

VersionInfo is the parsed result of an FXP_VERSION packet: the server's advertised protocol version and any name/value protocol extensions that follow.

func ParseVersion

func ParseVersion(payload []byte) (VersionInfo, error)

ParseVersion decodes an FXP_VERSION payload (Net::SFTP::Session#do_version): the server version followed by zero or more (name, data) extension pairs until the buffer is exhausted.

type Writer

type Writer struct {
	// contains filtered or unexported fields
}

Writer accumulates SSH2-encoded values, mirroring Net::SSH::Buffer's write_* methods byte-for-byte: 32-bit and 64-bit integers in network byte order, length-prefixed strings, single bytes, and 'C'-rule booleans.

func NewWriter

func NewWriter() *Writer

NewWriter returns an empty Writer.

func (*Writer) Bytes

func (w *Writer) Bytes() []byte

Bytes returns the accumulated payload. The returned slice aliases the Writer's internal storage, so callers that retain it must not mutate the Writer further.

func (*Writer) Len

func (w *Writer) Len() int

Len reports the number of bytes written so far.

func (*Writer) PutByte

func (w *Writer) PutByte(b byte) *Writer

PutByte appends a single byte (Net::SSH::Buffer#write_byte).

func (*Writer) WriteBool

func (w *Writer) WriteBool(v bool) *Writer

WriteBool appends one byte: 1 for true, 0 for false (#write_bool).

func (*Writer) WriteBytes

func (w *Writer) WriteBytes(b []byte) *Writer

WriteBytes appends a length-prefixed byte slice (the binary form of #write_string).

func (*Writer) WriteRaw

func (w *Writer) WriteRaw(b []byte) *Writer

WriteRaw appends bytes with no length prefix (the :raw element type used when embedding an already-serialised attributes blob).

func (*Writer) WriteString

func (w *Writer) WriteString(s string) *Writer

WriteString appends a uint32 length prefix followed by the raw bytes (#write_string). The string is treated as binary, exactly as MRI does.

func (*Writer) WriteUint32

func (w *Writer) WriteUint32(v uint32) *Writer

WriteUint32 appends a 32-bit network-byte-order integer (#write_long).

func (*Writer) WriteUint64

func (w *Writer) WriteUint64(v uint64) *Writer

WriteUint64 appends a 64-bit network-byte-order integer (#write_int64). MRI splits the value into a (hi, lo) pair of 32-bit words; the result is identical to a single big-endian uint64.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL