Documentation
¶
Overview ¶
Package netftp is a pure-Go (CGO=0) reimplementation of the deterministic, interpreter-independent core of Ruby's Net::FTP (MRI's net-ftp 0.3.9).
It is the protocol codec: it builds the FTP command bytes a client sends on the control connection, parses the 3-digit (optionally multiline) replies a server returns, extracts the data-connection address from PASV / EPSV responses, and parses MLSD / MLST entries and quoted pathnames — everything Net::FTP does that is pure computation over bytes, with no I/O.
The control and data sockets — connecting, reading, writing, TLS — are the host's job. They are SEAMS: this package never opens a socket. The host (go-embedded-ruby's rbgo) feeds reply text in and writes the command bytes this package produces out. Every observable byte — command line, reply classification, PASV/EPSV host:port, MLSx facts — matches MRI's net-ftp byte-for-byte, validated against the `ruby -rnet/ftp` binary.
Index ¶
- Constants
- Variables
- func AbortAccepted(resp string) bool
- func AcctCommand(acct string) string
- func AnonymousLogin(user string, passwdGiven bool) (string, bool)
- func CheckDelete(resp string) error
- func CheckRenameFrom(resp string) error
- func ClassifyReply(resp string) (string, error)
- func CwdCommand(dirname string) string
- func DeleCommand(filename string) string
- func GetMultiline(read LineReader) (string, error)
- func GetResp(read LineReader) (string, error)
- func HelpCommand(arg string) string
- func ListCommand(args ...string) string
- func MdtmCommand(filename string) string
- func MdtmResult(resp string) (string, bool)
- func MkdCommand(dirname string) string
- func MlsdCommand(pathname string) string
- func MlstCommand(pathname string) string
- func NlstCommand(dir string) string
- func OptionCommand(name, params string) string
- func Parse227(resp string, usePasvIP bool, remoteAddr string) (host string, port int, err error)
- func Parse229(resp string, remoteAddr string) (host string, port int, err error)
- func Parse257(resp string) (string, error)
- func PassCommand(passwd string) string
- func PasvIPv6Host(s string) string
- func PutLine(line string) (string, error)
- func ReplyBody(resp string) (string, bool)
- func ReplyCode(resp string) string
- func RetrCommand(filename string) string
- func RmdCommand(dirname string) string
- func RnfrCommand(fromname string) string
- func RntoCommand(toname string) string
- func Sanitize(s string) string
- func SendEPort(host string, port int) string
- func SendPort(host string, port int) string
- func SiteCommand(arg string) string
- func SizeCommand(filename string) string
- func SizeResult(resp string) (int, error)
- func StatCommand(pathname string) string
- func StorCommand(filename string) string
- func SystResult(resp string) (string, error)
- func TypeCommand(binary bool) string
- func UserCommand(user string) string
- func VoidResp(read LineReader) error
- type FTPError
- type FTPErrorKind
- type FactKind
- type FactValue
- type LineReader
- type LoginStep
- type MLSxEntry
- func (e MLSxEntry) Appendable() bool
- func (e MLSxEntry) Creatable() bool
- func (e MLSxEntry) Deletable() bool
- func (e MLSxEntry) DirectoryMakable() bool
- func (e MLSxEntry) Enterable() bool
- func (e MLSxEntry) IsDirectory() bool
- func (e MLSxEntry) IsFile() bool
- func (e MLSxEntry) Listable() bool
- func (e MLSxEntry) Purgeable() bool
- func (e MLSxEntry) Readable() bool
- func (e MLSxEntry) Renamable() bool
- func (e MLSxEntry) Writable() bool
- type MLSxTime
Constants ¶
const AborCommand = "ABOR"
AborCommand is the ABOR command (abort); MRI sends it out-of-band.
const AnonymousPassword = "anonymous@"
AnonymousPassword returns the password Net::FTP substitutes for an anonymous login when none is given: "anonymous@" (login: user == "anonymous" and passwd == nil → passwd = "anonymous@").
const CRLF = "\r\n"
CRLF is the line terminator Net::FTP appends to every control-connection command (Net::FTP::CRLF).
const CdupCommand = "CDUP"
CdupCommand is the CDUP command sent by chdir("..") before falling back to CWD on a 500 reply.
const EpsvCommand = "EPSV"
EpsvCommand is the EPSV command (passive transfers, extended/IPv6).
const FeatCommand = "FEAT"
FeatCommand is the FEAT command (features).
const NoopCommand = "NOOP"
NoopCommand is the NOOP command (noop).
const PasvCommand = "PASV"
PasvCommand is the PASV command (set_socket / passive transfers, IPv4).
const PwdCommand = "PWD"
PwdCommand is the PWD command (pwd / getdir).
const QuitCommand = "QUIT"
QuitCommand is the QUIT command (quit).
const SystCommand = "SYST"
SystCommand is the SYST command (system / .system).
Variables ¶
var ErrLineHasCRLF = errors.New("A line must not contain CR or LF")
ErrLineHasCRLF is returned by PutLine when a command line contains a CR or LF, mirroring MRI's putline: `raise ArgumentError, "A line must not contain CR or LF"`.
Functions ¶
func AbortAccepted ¶
AbortAccepted reports whether an ABOR reply is one MRI accepts (426, 226, or 225); otherwise MRI raises FTPProtoError. The reply's first three characters are tested.
func AcctCommand ¶
AcctCommand builds the ACCT command (MRI: 'ACCT ' + acct).
func AnonymousLogin ¶
AnonymousLogin reports the password Net::FTP substitutes when logging in as "anonymous" with no password: ("anonymous@", true). For any other user, or when a password is already supplied, it returns ("", false).
func CheckDelete ¶
CheckDelete classifies the reply to a DELE command, mirroring MRI delete:
- "250…" → success (nil).
- "5…" → FTPPermError.
- anything else → FTPReplyError.
func CheckRenameFrom ¶
CheckRenameFrom classifies the reply to an RNFR command, mirroring MRI rename: a reply not beginning with '3' is an FTPReplyError; otherwise RNTO follows.
func ClassifyReply ¶
ClassifyReply applies MRI's getresp classification to an assembled reply:
- 1yz / 2yz / 3yz → returns the reply with a nil error (success).
- 4yz → FTPTempError.
- 5yz → FTPPermError.
- anything else → FTPProtoError.
The first byte of the code drives the decision, as in MRI's case/when on /\A[123]/, /\A4/, /\A5/.
func CwdCommand ¶
CwdCommand builds the CWD command (MRI chdir: "CWD #{dirname}").
func DeleCommand ¶
DeleCommand builds the DELE command (MRI delete: "DELE #{filename}").
func GetMultiline ¶
func GetMultiline(read LineReader) (string, error)
GetMultiline assembles one complete reply from the control connection, mirroring MRI's getmultiline. It reads the first line; if that line is a multiline opener ("NNN-..."), it keeps reading until a line begins with "NNN " (the code followed by a space). The lines are joined with "\n" and a trailing "\n" is appended, exactly as MRI returns them.
read is the host-supplied socket seam; any error it returns (EOF, I/O) is propagated unchanged.
func GetResp ¶
func GetResp(read LineReader) (string, error)
GetResp reads one reply (GetMultiline) and classifies it (ClassifyReply), mirroring MRI's getresp. On success it returns the assembled reply; on a 4yz/5yz/other reply it returns the matching FTPError; a read error from the seam is propagated unchanged.
func HelpCommand ¶
HelpCommand builds the HELP command (MRI help: "HELP", optionally " " + arg).
func ListCommand ¶
ListCommand builds the LIST command, appending each argument separated by a space, mirroring MRI's list: `cmd = "LIST"; args.each { cmd = "#{cmd} #{arg}" }`.
func MdtmCommand ¶
MdtmCommand builds the MDTM command (MRI mdtm: "MDTM #{filename}").
func MdtmResult ¶
MdtmResult extracts the raw "YYYYMMDDhhmmss" timestamp from an MDTM reply, mirroring MRI mdtm: when the reply begins with "213" the body after "NNN " is returned (ok true); otherwise MRI returns nil — here ok is false.
func MkdCommand ¶
MkdCommand builds the MKD command (MRI mkdir: "MKD #{dirname}").
func MlsdCommand ¶
MlsdCommand builds the MLSD command (MRI mlsd: "MLSD", optionally " #{pathname}").
func MlstCommand ¶
MlstCommand builds the MLST command (MRI mlst: "MLST", optionally " #{pathname}").
func NlstCommand ¶
NlstCommand builds the NLST command (MRI nlst: "NLST", optionally " #{dir}").
func OptionCommand ¶
OptionCommand builds the OPTS command (MRI option: "OPTS #{name}", optionally " #{params}").
func Parse227 ¶
Parse227 parses a 227 (Entering Passive Mode) reply, mirroring MRI parse227.
- If resp does not start with "227" → FTPReplyError.
- If the "(h1,h2,h3,h4,p1,p2)" pattern is absent → FTPProtoError.
- Otherwise the port is (p1<<8)+p2.
The host is the seam: MRI returns the encoded h1.h2.h3.h4 when @use_pasv_ip is true, otherwise the control socket's remote address. usePasvIP selects between the two; remoteAddr supplies the socket's address for the false branch (the host passes what the data connection should dial).
func Parse229 ¶
Parse229 parses a 229 (Extended Passive Mode) reply, mirroring MRI parse229.
- If resp does not start with "229" → FTPReplyError.
- If the "(|||port|)" pattern (delimiter repeated three times, then the port, then the delimiter once) is absent → FTPProtoError.
EPSV does not encode a host, so MRI returns the control socket's remote address; remoteAddr is that seam value, returned unchanged as host.
func Parse257 ¶
Parse257 extracts the quoted pathname from a 257 reply, mirroring MRI parse257.
- If resp does not start with "257" → FTPReplyError.
- Otherwise it returns the contents of the first "..." with doubled quotes ("") collapsed to a single quote. When no quoted string is present MRI's `.to_s` yields "" — this returns "" too.
func PassCommand ¶
PassCommand builds the PASS command (MRI: 'PASS ' + passwd).
func PasvIPv6Host ¶
PasvIPv6Host turns the comma-separated host group of an IPv6 PASV reply into a colon-separated hextet address, mirroring parse_pasv_ipv6_host: `s.split(",").map { "%02x" % i.to_i }.each_slice(2).map(&:join).join(":")`.
func PutLine ¶
PutLine validates a command line and appends CRLF, returning the bytes MRI's putline writes to the control socket. The actual write is the host's seam.
func ReplyBody ¶
ReplyBody returns the text after the 3-character code and one space, mirroring MRI's get_body: `resp.slice(/\A[0-9a-zA-Z]{3} (.*)$/, 1)`. The match is anchored at the start and stops at the first newline ($). It returns "", false when the reply does not start with "NNN " (a valid code plus a space).
func ReplyCode ¶
ReplyCode returns the three-character reply code at the start of an assembled reply (MRI's `@last_response[0, 3]`). For a reply shorter than three characters it returns the whole reply, matching Ruby's slice semantics.
func RetrCommand ¶
RetrCommand builds the RETR command used by the binary/text get helpers.
func RmdCommand ¶
RmdCommand builds the RMD command (MRI rmdir: "RMD #{dirname}").
func RnfrCommand ¶
RnfrCommand builds the RNFR command (MRI rename: "RNFR #{fromname}").
func RntoCommand ¶
RntoCommand builds the RNTO command (MRI rename: "RNTO #{toname}").
func Sanitize ¶
Sanitize hides the password in a PASS command for debug output, mirroring MRI's sanitize: a line matching /^PASS /i keeps its first five characters and has the rest replaced with '*'. Any other line is returned unchanged.
func SendEPort ¶
SendEPort builds the EPRT command for an active-mode IPv6 data connection, mirroring MRI sendport: sprintf("EPRT |2|%s|%d|", host, port).
func SendPort ¶
SendPort builds the PORT command for an active-mode IPv4 data connection, mirroring MRI sendport: "PORT " + (host.split(".") + port.divmod(256)).join(","). host is dotted-quad; the port's high and low bytes are appended.
func SiteCommand ¶
SiteCommand builds the SITE command (MRI site: "SITE " + arg).
func SizeCommand ¶
SizeCommand builds the SIZE command (MRI size: "SIZE #{filename}").
func SizeResult ¶
SizeResult extracts the file size from a SIZE reply, mirroring MRI size: a reply not beginning with "213" is an FTPReplyError; otherwise the body after "NNN " is read as an integer (String#to_i).
func StatCommand ¶
StatCommand builds the STAT command (MRI status: "STAT", optionally " #{pathname}").
func StorCommand ¶
StorCommand builds the STOR command used by the put helpers.
func SystResult ¶
SystResult extracts the system string from a SYST reply, mirroring MRI system: a reply not beginning with "215" is an FTPReplyError; otherwise the body after "NNN " is returned.
func TypeCommand ¶
TypeCommand builds the TYPE command for the current transfer mode, mirroring send_type_command: "TYPE I" when binary, "TYPE A" otherwise.
func UserCommand ¶
UserCommand builds the USER command (MRI: 'USER ' + user).
func VoidResp ¶
func VoidResp(read LineReader) error
VoidResp reads a reply and requires it to begin with '2', mirroring MRI's voidresp (`raise FTPReplyError, resp if !resp.start_with?("2")`). Note MRI calls getresp first, so a 4yz/5yz reply surfaces as Temp/Perm before the "starts with 2" check; this preserves that ordering.
Types ¶
type FTPError ¶
type FTPError struct {
// Kind names the concrete Net::FTP subclass this error represents.
Kind FTPErrorKind
// Message is the reply (or other) text MRI passes to `raise`.
Message string
}
FTPError is the base of the Net::FTP error family (Net::FTPError).
type FTPErrorKind ¶
type FTPErrorKind int
FTPErrorKind enumerates the concrete Net::FTP error subclasses.
const ( // KindReply is Net::FTPReplyError — an unexpected reply code. KindReply FTPErrorKind = iota // KindTemp is Net::FTPTempError — a 4yz transient negative reply. KindTemp // KindPerm is Net::FTPPermError — a 5yz permanent negative reply. KindPerm // KindProto is Net::FTPProtoError — a reply that is not 1/2/3/4/5yz, or // otherwise malformed. KindProto // KindConnection is Net::FTPConnectionError — a control-connection failure. KindConnection )
type FactValue ¶
type FactValue struct {
Kind FactKind
// Str holds the value for string-typed facts (verbatim, case-folded, or the
// raw text for any fact without a dedicated parser).
Str string
// Int holds the value for decimal/octal-typed facts (size, unix.mode, …).
Int int
// Time holds the value for time-typed facts (modify, create, unix.*time).
Time MLSxTime
}
FactValue is a parsed MLSx fact value. Exactly one shape is populated, selected by Kind. The host maps these onto Ruby values (Integer, Time, String).
type LineReader ¶
LineReader yields successive raw lines from the control connection. It is the socket seam: returning an error stands in for MRI's EOFError / read failure.
type LoginStep ¶
type LoginStep int
LoginStep is one rung of MRI's login USER → PASS → ACCT ladder.
const ( // LoginSendPass means the previous reply began with '3', so send PASS next. // When the credential for the next step is missing MRI raises FTPReplyError // (see LoginNext's error return). LoginSendPass LoginStep = iota // LoginSendAcct means send ACCT next (the reply still began with '3'). LoginSendAcct // LoginDone means the ladder is complete; the final reply must start with // '2' or LoginNext returns FTPReplyError. LoginDone )
func LoginNext ¶
LoginNext decides the next login action from the latest reply, mirroring the flow in MRI's login. afterPass distinguishes the reply to USER (false) from the reply to PASS (true), so a '3' reply maps to LoginSendPass or LoginSendAcct respectively. A reply that does not begin with '3' ends the ladder: LoginDone, with FTPReplyError when it does not begin with '2'.
type MLSxEntry ¶
type MLSxEntry struct {
// Facts maps each fact name (already lower-cased, as MRI stores them) to its
// parsed value. Insertion-order is not preserved (Go map); use FactOrder for
// the order facts appeared, if needed.
Facts map[string]FactValue
// FactOrder lists fact names in the order they appeared in the entry — MRI's
// Hash preserves insertion order; this exposes the same sequence.
FactOrder []string
// Pathname is the entry's pathname (the text after the first space).
Pathname string
}
MLSxEntry is a parsed MLST/MLSD entry: the facts (keyed by lower-cased name) and the pathname, mirroring Net::FTP::MLSxEntry.
func ParseMLSxEntry ¶
ParseMLSxEntry parses one MLST/MLSD line into an MLSxEntry, mirroring MRI parse_mlsx_entry:
- The line is chomped (trailing "\n"/"\r\n"/"\r" removed) then split on the first space into the facts blob and the pathname.
- A line with no space (no pathname) → FTPProtoError with the raw entry.
- Each "name=value;" pair is scanned out; the name is lower-cased and its value parsed by the matching FACT_PARSERS entry.
A fact value that fails its parser (an invalid time-val) propagates that FTPProtoError, exactly as the lambda would raise inside MRI.
func (MLSxEntry) Appendable ¶
Appendable reports MLSxEntry#appendable? (perm includes 'a').
func (MLSxEntry) DirectoryMakable ¶
DirectoryMakable reports MLSxEntry#directory_makable? (perm includes 'm').
func (MLSxEntry) IsDirectory ¶
IsDirectory reports whether the entry's type fact is dir, cdir, or pdir (MLSxEntry#directory?).
