ftp.go 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143
  1. // Package ftp implements a FTP client as described in RFC 959.
  2. //
  3. // A textproto.Error is returned for errors at the protocol level.
  4. package ftp
  5. import (
  6. "bufio"
  7. "context"
  8. "crypto/tls"
  9. "errors"
  10. "io"
  11. "net"
  12. "net/textproto"
  13. "strconv"
  14. "strings"
  15. "time"
  16. "github.com/hashicorp/go-multierror"
  17. )
  18. const (
  19. // 30 seconds was chosen as it's the
  20. // same duration as http.DefaultTransport's timeout.
  21. DefaultDialTimeout = 30 * time.Second
  22. )
  23. // EntryType describes the different types of an Entry.
  24. type EntryType int
  25. // The differents types of an Entry
  26. const (
  27. EntryTypeFile EntryType = iota
  28. EntryTypeFolder
  29. EntryTypeLink
  30. )
  31. // TransferType denotes the formats for transferring Entries.
  32. type TransferType string
  33. // The different transfer types
  34. const (
  35. TransferTypeBinary = TransferType("I")
  36. TransferTypeASCII = TransferType("A")
  37. )
  38. // Time format used by the MDTM and MFMT commands
  39. const timeFormat = "20060102150405"
  40. // ServerConn represents the connection to a remote FTP server.
  41. // A single connection only supports one in-flight data connection.
  42. // It is not safe to be called concurrently.
  43. type ServerConn struct {
  44. options *dialOptions
  45. conn *textproto.Conn // connection wrapper for text protocol
  46. netConn net.Conn // underlying network connection
  47. host string
  48. // Server capabilities discovered at runtime
  49. features map[string]string
  50. skipEPSV bool
  51. mlstSupported bool
  52. mfmtSupported bool
  53. mdtmSupported bool
  54. mdtmCanWrite bool
  55. usePRET bool
  56. }
  57. // DialOption represents an option to start a new connection with Dial
  58. type DialOption struct {
  59. setup func(do *dialOptions)
  60. }
  61. // dialOptions contains all the options set by DialOption.setup
  62. type dialOptions struct {
  63. context context.Context
  64. dialer net.Dialer
  65. tlsConfig *tls.Config
  66. explicitTLS bool
  67. disableEPSV bool
  68. disableUTF8 bool
  69. disableMLSD bool
  70. writingMDTM bool
  71. forceListHidden bool
  72. location *time.Location
  73. debugOutput io.Writer
  74. dialFunc func(network, address string) (net.Conn, error)
  75. shutTimeout time.Duration // time to wait for data connection closing status
  76. }
  77. // Entry describes a file and is returned by List().
  78. type Entry struct {
  79. Name string
  80. Target string // target of symbolic link
  81. Type EntryType
  82. Size uint64
  83. Time time.Time
  84. }
  85. // Response represents a data-connection
  86. type Response struct {
  87. conn net.Conn
  88. c *ServerConn
  89. closed bool
  90. }
  91. // Dial connects to the specified address with optional options
  92. func Dial(addr string, options ...DialOption) (*ServerConn, error) {
  93. do := &dialOptions{}
  94. for _, option := range options {
  95. option.setup(do)
  96. }
  97. if do.location == nil {
  98. do.location = time.UTC
  99. }
  100. dialFunc := do.dialFunc
  101. if dialFunc == nil {
  102. ctx := do.context
  103. if ctx == nil {
  104. ctx = context.Background()
  105. }
  106. if _, ok := ctx.Deadline(); !ok {
  107. var cancel context.CancelFunc
  108. ctx, cancel = context.WithTimeout(ctx, DefaultDialTimeout)
  109. defer cancel()
  110. }
  111. if do.tlsConfig != nil && !do.explicitTLS {
  112. dialFunc = func(network, address string) (net.Conn, error) {
  113. tlsDialer := &tls.Dialer{
  114. NetDialer: &do.dialer,
  115. Config: do.tlsConfig,
  116. }
  117. return tlsDialer.DialContext(ctx, network, addr)
  118. }
  119. } else {
  120. dialFunc = func(network, address string) (net.Conn, error) {
  121. return do.dialer.DialContext(ctx, network, addr)
  122. }
  123. }
  124. }
  125. tconn, err := dialFunc("tcp", addr)
  126. if err != nil {
  127. return nil, err
  128. }
  129. // Use the resolved IP address in case addr contains a domain name
  130. // If we use the domain name, we might not resolve to the same IP.
  131. remoteAddr := tconn.RemoteAddr().(*net.TCPAddr)
  132. c := &ServerConn{
  133. options: do,
  134. features: make(map[string]string),
  135. conn: textproto.NewConn(do.wrapConn(tconn)),
  136. netConn: tconn,
  137. host: remoteAddr.IP.String(),
  138. }
  139. _, _, err = c.conn.ReadResponse(StatusReady)
  140. if err != nil {
  141. _ = c.Quit()
  142. return nil, err
  143. }
  144. if do.explicitTLS {
  145. if err := c.authTLS(); err != nil {
  146. _ = c.Quit()
  147. return nil, err
  148. }
  149. tconn = tls.Client(tconn, do.tlsConfig)
  150. c.conn = textproto.NewConn(do.wrapConn(tconn))
  151. }
  152. return c, nil
  153. }
  154. // DialWithTimeout returns a DialOption that configures the ServerConn with specified timeout
  155. func DialWithTimeout(timeout time.Duration) DialOption {
  156. return DialOption{func(do *dialOptions) {
  157. do.dialer.Timeout = timeout
  158. }}
  159. }
  160. // DialWithShutTimeout returns a DialOption that configures the ServerConn with
  161. // maximum time to wait for the data closing status on control connection
  162. // and nudging the control connection deadline before reading status.
  163. func DialWithShutTimeout(shutTimeout time.Duration) DialOption {
  164. return DialOption{func(do *dialOptions) {
  165. do.shutTimeout = shutTimeout
  166. }}
  167. }
  168. // DialWithDialer returns a DialOption that configures the ServerConn with specified net.Dialer
  169. func DialWithDialer(dialer net.Dialer) DialOption {
  170. return DialOption{func(do *dialOptions) {
  171. do.dialer = dialer
  172. }}
  173. }
  174. // DialWithNetConn returns a DialOption that configures the ServerConn with the underlying net.Conn
  175. //
  176. // Deprecated: Use [DialWithDialFunc] instead
  177. func DialWithNetConn(conn net.Conn) DialOption {
  178. return DialWithDialFunc(func(network, address string) (net.Conn, error) {
  179. return conn, nil
  180. })
  181. }
  182. // DialWithDisabledEPSV returns a DialOption that configures the ServerConn with EPSV disabled
  183. // Note that EPSV is only used when advertised in the server features.
  184. func DialWithDisabledEPSV(disabled bool) DialOption {
  185. return DialOption{func(do *dialOptions) {
  186. do.disableEPSV = disabled
  187. }}
  188. }
  189. // DialWithDisabledUTF8 returns a DialOption that configures the ServerConn with UTF8 option disabled
  190. func DialWithDisabledUTF8(disabled bool) DialOption {
  191. return DialOption{func(do *dialOptions) {
  192. do.disableUTF8 = disabled
  193. }}
  194. }
  195. // DialWithDisabledMLSD returns a DialOption that configures the ServerConn with MLSD option disabled
  196. //
  197. // This is useful for servers which advertise MLSD (eg some versions
  198. // of Serv-U) but don't support it properly.
  199. func DialWithDisabledMLSD(disabled bool) DialOption {
  200. return DialOption{func(do *dialOptions) {
  201. do.disableMLSD = disabled
  202. }}
  203. }
  204. // DialWithWritingMDTM returns a DialOption making ServerConn use MDTM to set file time
  205. //
  206. // This option addresses a quirk in the VsFtpd server which doesn't support
  207. // the MFMT command for setting file time like other servers but by default
  208. // uses the MDTM command with non-standard arguments for that.
  209. // See "mdtm_write" in https://security.appspot.com/vsftpd/vsftpd_conf.html
  210. func DialWithWritingMDTM(enabled bool) DialOption {
  211. return DialOption{func(do *dialOptions) {
  212. do.writingMDTM = enabled
  213. }}
  214. }
  215. // DialWithForceListHidden returns a DialOption making ServerConn use LIST -a to include hidden files and folders in directory listings
  216. //
  217. // This is useful for servers that do not do this by default, but it forces the use of the LIST command
  218. // even if the server supports MLST.
  219. func DialWithForceListHidden(enabled bool) DialOption {
  220. return DialOption{func(do *dialOptions) {
  221. do.forceListHidden = enabled
  222. }}
  223. }
  224. // DialWithLocation returns a DialOption that configures the ServerConn with specified time.Location
  225. // The location is used to parse the dates sent by the server which are in server's timezone
  226. func DialWithLocation(location *time.Location) DialOption {
  227. return DialOption{func(do *dialOptions) {
  228. do.location = location
  229. }}
  230. }
  231. // DialWithContext returns a DialOption that configures the ServerConn with specified context
  232. // The context will be used for the initial connection setup
  233. func DialWithContext(ctx context.Context) DialOption {
  234. return DialOption{func(do *dialOptions) {
  235. do.context = ctx
  236. }}
  237. }
  238. // DialWithTLS returns a DialOption that configures the ServerConn with specified TLS config
  239. //
  240. // If called together with the DialWithDialFunc option, the DialWithDialFunc function
  241. // will be used when dialing new connections but regardless of the function,
  242. // the connection will be treated as a TLS connection.
  243. func DialWithTLS(tlsConfig *tls.Config) DialOption {
  244. return DialOption{func(do *dialOptions) {
  245. do.tlsConfig = tlsConfig
  246. }}
  247. }
  248. // DialWithExplicitTLS returns a DialOption that configures the ServerConn to be upgraded to TLS
  249. // See DialWithTLS for general TLS documentation
  250. func DialWithExplicitTLS(tlsConfig *tls.Config) DialOption {
  251. return DialOption{func(do *dialOptions) {
  252. do.explicitTLS = true
  253. do.tlsConfig = tlsConfig
  254. }}
  255. }
  256. // DialWithDebugOutput returns a DialOption that configures the ServerConn to write to the Writer
  257. // everything it reads from the server
  258. func DialWithDebugOutput(w io.Writer) DialOption {
  259. return DialOption{func(do *dialOptions) {
  260. do.debugOutput = w
  261. }}
  262. }
  263. // DialWithDialFunc returns a DialOption that configures the ServerConn to use the
  264. // specified function to establish both control and data connections
  265. //
  266. // If used together with the DialWithNetConn option, the DialWithNetConn
  267. // takes precedence for the control connection, while data connections will
  268. // be established using function specified with the DialWithDialFunc option
  269. func DialWithDialFunc(f func(network, address string) (net.Conn, error)) DialOption {
  270. return DialOption{func(do *dialOptions) {
  271. do.dialFunc = f
  272. }}
  273. }
  274. func (o *dialOptions) wrapConn(netConn net.Conn) io.ReadWriteCloser {
  275. if o.debugOutput == nil {
  276. return netConn
  277. }
  278. return newDebugWrapper(netConn, o.debugOutput)
  279. }
  280. func (o *dialOptions) wrapStream(rd io.ReadCloser) io.ReadCloser {
  281. if o.debugOutput == nil {
  282. return rd
  283. }
  284. return newStreamDebugWrapper(rd, o.debugOutput)
  285. }
  286. // Connect is an alias to Dial, for backward compatibility
  287. //
  288. // Deprecated: Use [Dial] instead
  289. func Connect(addr string) (*ServerConn, error) {
  290. return Dial(addr)
  291. }
  292. // DialTimeout initializes the connection to the specified ftp server address.
  293. //
  294. // Deprecated: Use [Dial] with [DialWithTimeout] option instead
  295. func DialTimeout(addr string, timeout time.Duration) (*ServerConn, error) {
  296. return Dial(addr, DialWithTimeout(timeout))
  297. }
  298. // Login authenticates the client with specified user and password.
  299. //
  300. // "anonymous"/"anonymous" is a common user/password scheme for FTP servers
  301. // that allows anonymous read-only accounts.
  302. func (c *ServerConn) Login(user, password string) error {
  303. code, message, err := c.cmd(-1, "USER %s", user)
  304. if err != nil {
  305. return err
  306. }
  307. switch code {
  308. case StatusLoggedIn:
  309. case StatusUserOK:
  310. _, _, err = c.cmd(StatusLoggedIn, "PASS %s", password)
  311. if err != nil {
  312. return err
  313. }
  314. default:
  315. return errors.New(message)
  316. }
  317. // Probe features
  318. err = c.feat()
  319. if err != nil {
  320. return err
  321. }
  322. if _, mlstSupported := c.features["MLST"]; mlstSupported && !c.options.disableMLSD {
  323. c.mlstSupported = true
  324. }
  325. _, c.usePRET = c.features["PRET"]
  326. _, c.mfmtSupported = c.features["MFMT"]
  327. _, c.mdtmSupported = c.features["MDTM"]
  328. c.mdtmCanWrite = c.mdtmSupported && c.options.writingMDTM
  329. // Switch to binary mode
  330. if err = c.Type(TransferTypeBinary); err != nil {
  331. return err
  332. }
  333. // Switch to UTF-8
  334. if !c.options.disableUTF8 {
  335. err = c.setUTF8()
  336. }
  337. // If using implicit TLS, make data connections also use TLS
  338. if c.options.tlsConfig != nil {
  339. if _, _, err = c.cmd(StatusCommandOK, "PBSZ 0"); err != nil {
  340. return err
  341. }
  342. if _, _, err = c.cmd(StatusCommandOK, "PROT P"); err != nil {
  343. return err
  344. }
  345. }
  346. return err
  347. }
  348. // authTLS upgrades the connection to use TLS
  349. func (c *ServerConn) authTLS() error {
  350. _, _, err := c.cmd(StatusAuthOK, "AUTH TLS")
  351. return err
  352. }
  353. // feat issues a FEAT FTP command to list the additional commands supported by
  354. // the remote FTP server.
  355. // FEAT is described in RFC 2389
  356. func (c *ServerConn) feat() error {
  357. code, message, err := c.cmd(-1, "FEAT")
  358. if err != nil {
  359. return err
  360. }
  361. if code != StatusSystem {
  362. // The server does not support the FEAT command. This is not an
  363. // error: we consider that there is no additional feature.
  364. return nil
  365. }
  366. lines := strings.Split(message, "\n")
  367. for _, line := range lines {
  368. if !strings.HasPrefix(line, " ") {
  369. continue
  370. }
  371. line = strings.TrimSpace(line)
  372. featureElements := strings.SplitN(line, " ", 2)
  373. command := featureElements[0]
  374. var commandDesc string
  375. if len(featureElements) == 2 {
  376. commandDesc = featureElements[1]
  377. }
  378. c.features[command] = commandDesc
  379. }
  380. return nil
  381. }
  382. // setUTF8 issues an "OPTS UTF8 ON" command.
  383. func (c *ServerConn) setUTF8() error {
  384. if _, ok := c.features["UTF8"]; !ok {
  385. return nil
  386. }
  387. code, message, err := c.cmd(-1, "OPTS UTF8 ON")
  388. if err != nil {
  389. return err
  390. }
  391. // Workaround for FTP servers, that does not support this option.
  392. if code == StatusBadArguments || code == StatusNotImplementedParameter {
  393. return nil
  394. }
  395. // The ftpd "filezilla-server" has FEAT support for UTF8, but always returns
  396. // "202 UTF8 mode is always enabled. No need to send this command." when
  397. // trying to use it. That's OK
  398. if code == StatusCommandNotImplemented {
  399. return nil
  400. }
  401. if code != StatusCommandOK {
  402. return errors.New(message)
  403. }
  404. return nil
  405. }
  406. // epsv issues an "EPSV" command to get a port number for a data connection.
  407. func (c *ServerConn) epsv() (port int, err error) {
  408. _, line, err := c.cmd(StatusExtendedPassiveMode, "EPSV")
  409. if err != nil {
  410. return 0, err
  411. }
  412. start := strings.Index(line, "|||")
  413. end := strings.LastIndex(line, "|")
  414. if start == -1 || end == -1 {
  415. return 0, errors.New("invalid EPSV response format")
  416. }
  417. port, err = strconv.Atoi(line[start+3 : end])
  418. return port, err
  419. }
  420. // pasv issues a "PASV" command to get a port number for a data connection.
  421. func (c *ServerConn) pasv() (host string, port int, err error) {
  422. _, line, err := c.cmd(StatusPassiveMode, "PASV")
  423. if err != nil {
  424. return "", 0, err
  425. }
  426. // PASV response format : 227 Entering Passive Mode (h1,h2,h3,h4,p1,p2).
  427. start := strings.Index(line, "(")
  428. end := strings.LastIndex(line, ")")
  429. if start == -1 || end == -1 {
  430. return "", 0, errors.New("invalid PASV response format")
  431. }
  432. // We have to split the response string
  433. pasvData := strings.Split(line[start+1:end], ",")
  434. if len(pasvData) < 6 {
  435. return "", 0, errors.New("invalid PASV response format")
  436. }
  437. // Let's compute the port number
  438. portPart1, err := strconv.Atoi(pasvData[4])
  439. if err != nil {
  440. return "", 0, err
  441. }
  442. portPart2, err := strconv.Atoi(pasvData[5])
  443. if err != nil {
  444. return "", 0, err
  445. }
  446. // Recompose port
  447. port = portPart1*256 + portPart2
  448. // Make the IP address to connect to
  449. host = strings.Join(pasvData[0:4], ".")
  450. return host, port, nil
  451. }
  452. // getDataConnPort returns a host, port for a new data connection
  453. // it uses the best available method to do so
  454. func (c *ServerConn) getDataConnPort() (string, int, error) {
  455. if !c.options.disableEPSV && !c.skipEPSV {
  456. if port, err := c.epsv(); err == nil {
  457. return c.host, port, nil
  458. }
  459. // if there is an error, skip EPSV for the next attempts
  460. c.skipEPSV = true
  461. }
  462. return c.pasv()
  463. }
  464. // openDataConn creates a new FTP data connection.
  465. func (c *ServerConn) openDataConn() (net.Conn, error) {
  466. host, port, err := c.getDataConnPort()
  467. if err != nil {
  468. return nil, err
  469. }
  470. addr := net.JoinHostPort(host, strconv.Itoa(port))
  471. if c.options.dialFunc != nil {
  472. return c.options.dialFunc("tcp", addr)
  473. }
  474. if c.options.tlsConfig != nil {
  475. // We don't use tls.DialWithDialer here (which does Dial, create
  476. // the Client and then do the Handshake) because it seems to
  477. // hang with some FTP servers, namely proftpd and pureftpd.
  478. //
  479. // Instead we do Dial, create the Client and wait for the first
  480. // Read or Write to trigger the Handshake.
  481. //
  482. // This means that if we are uploading a zero sized file, we
  483. // need to make sure we do the Handshake explicitly as Write
  484. // won't have been called. This is done in StorFrom().
  485. //
  486. // See: https://github.com/jlaffaye/ftp/issues/282
  487. conn, err := c.options.dialer.Dial("tcp", addr)
  488. if err != nil {
  489. return nil, err
  490. }
  491. tlsConn := tls.Client(conn, c.options.tlsConfig)
  492. return tlsConn, nil
  493. }
  494. return c.options.dialer.Dial("tcp", addr)
  495. }
  496. // cmd is a helper function to execute a command and check for the expected FTP
  497. // return code
  498. func (c *ServerConn) cmd(expected int, format string, args ...interface{}) (int, string, error) {
  499. _, err := c.conn.Cmd(format, args...)
  500. if err != nil {
  501. return 0, "", err
  502. }
  503. return c.conn.ReadResponse(expected)
  504. }
  505. // cmdDataConnFrom executes a command which require a FTP data connection.
  506. // Issues a REST FTP command to specify the number of bytes to skip for the transfer.
  507. func (c *ServerConn) cmdDataConnFrom(offset uint64, format string, args ...interface{}) (net.Conn, error) {
  508. // If server requires PRET send the PRET command to warm it up
  509. // See: https://tools.ietf.org/html/draft-dd-pret-00
  510. if c.usePRET {
  511. _, _, err := c.cmd(-1, "PRET "+format, args...)
  512. if err != nil {
  513. return nil, err
  514. }
  515. }
  516. conn, err := c.openDataConn()
  517. if err != nil {
  518. return nil, err
  519. }
  520. if offset != 0 {
  521. _, _, err = c.cmd(StatusRequestFilePending, "REST %d", offset)
  522. if err != nil {
  523. _ = conn.Close()
  524. return nil, err
  525. }
  526. }
  527. _, err = c.conn.Cmd(format, args...)
  528. if err != nil {
  529. _ = conn.Close()
  530. return nil, err
  531. }
  532. code, msg, err := c.conn.ReadResponse(-1)
  533. if err != nil {
  534. _ = conn.Close()
  535. return nil, err
  536. }
  537. if code != StatusAlreadyOpen && code != StatusAboutToSend {
  538. _ = conn.Close()
  539. return nil, &textproto.Error{Code: code, Msg: msg}
  540. }
  541. return conn, nil
  542. }
  543. // Type switches the transfer mode for the connection.
  544. func (c *ServerConn) Type(transferType TransferType) (err error) {
  545. _, _, err = c.cmd(StatusCommandOK, "TYPE "+string(transferType))
  546. return err
  547. }
  548. // NameList issues an NLST FTP command.
  549. func (c *ServerConn) NameList(path string) (entries []string, err error) {
  550. space := " "
  551. if path == "" {
  552. space = ""
  553. }
  554. conn, err := c.cmdDataConnFrom(0, "NLST%s%s", space, path)
  555. if err != nil {
  556. return nil, err
  557. }
  558. var errs *multierror.Error
  559. r := &Response{conn: conn, c: c}
  560. scanner := bufio.NewScanner(c.options.wrapStream(r))
  561. for scanner.Scan() {
  562. entries = append(entries, scanner.Text())
  563. }
  564. if err := scanner.Err(); err != nil {
  565. errs = multierror.Append(errs, err)
  566. }
  567. if err := r.Close(); err != nil {
  568. errs = multierror.Append(errs, err)
  569. }
  570. return entries, errs.ErrorOrNil()
  571. }
  572. // List issues a LIST FTP command.
  573. func (c *ServerConn) List(path string) (entries []*Entry, err error) {
  574. var cmd string
  575. var parser parseFunc
  576. if c.mlstSupported && !c.options.forceListHidden {
  577. cmd = "MLSD"
  578. parser = parseRFC3659ListLine
  579. } else {
  580. cmd = "LIST"
  581. if c.options.forceListHidden {
  582. cmd += " -a"
  583. }
  584. parser = parseListLine
  585. }
  586. space := " "
  587. if path == "" {
  588. space = ""
  589. }
  590. conn, err := c.cmdDataConnFrom(0, "%s%s%s", cmd, space, path)
  591. if err != nil {
  592. return nil, err
  593. }
  594. var errs *multierror.Error
  595. r := &Response{conn: conn, c: c}
  596. scanner := bufio.NewScanner(c.options.wrapStream(r))
  597. now := time.Now()
  598. for scanner.Scan() {
  599. entry, errParse := parser(scanner.Text(), now, c.options.location)
  600. if errParse == nil {
  601. entries = append(entries, entry)
  602. }
  603. }
  604. if err := scanner.Err(); err != nil {
  605. errs = multierror.Append(errs, err)
  606. }
  607. if err := r.Close(); err != nil {
  608. errs = multierror.Append(errs, err)
  609. }
  610. return entries, errs.ErrorOrNil()
  611. }
  612. // GetEntry issues a MLST FTP command which retrieves one single Entry using the
  613. // control connection. The returnedEntry will describe the current directory
  614. // when no path is given.
  615. func (c *ServerConn) GetEntry(path string) (entry *Entry, err error) {
  616. if !c.mlstSupported {
  617. return nil, &textproto.Error{Code: StatusNotImplemented, Msg: StatusText(StatusNotImplemented)}
  618. }
  619. space := " "
  620. if path == "" {
  621. space = ""
  622. }
  623. _, msg, err := c.cmd(StatusRequestedFileActionOK, "%s%s%s", "MLST", space, path)
  624. if err != nil {
  625. return nil, err
  626. }
  627. // The expected reply will look something like:
  628. //
  629. // 250-File details
  630. // Type=file;Size=1024;Modify=20220813133357; path
  631. // 250 End
  632. //
  633. // Multiple lines are allowed though, so it can also be in the form:
  634. //
  635. // 250-File details
  636. // Type=file;Size=1024; path
  637. // Modify=20220813133357; path
  638. // 250 End
  639. lines := strings.Split(msg, "\n")
  640. lc := len(lines)
  641. // lines must be a multi-line message with a length of 3 or more, and we
  642. // don't care about the first and last line
  643. if lc < 3 {
  644. return nil, errors.New("invalid response")
  645. }
  646. e := &Entry{}
  647. for _, l := range lines[1 : lc-1] {
  648. // According to RFC 3659, the entry lines must start with a space when passed over the
  649. // control connection. Some servers don't seem to add that space though. Both forms are
  650. // accepted here.
  651. if len(l) > 0 && l[0] == ' ' {
  652. l = l[1:]
  653. }
  654. // Some severs seem to send a blank line at the end which we ignore
  655. if l == "" {
  656. continue
  657. }
  658. if e, err = parseNextRFC3659ListLine(l, c.options.location, e); err != nil {
  659. return nil, err
  660. }
  661. }
  662. return e, nil
  663. }
  664. // IsTimePreciseInList returns true if client and server support the MLSD
  665. // command so List can return time with 1-second precision for all files.
  666. func (c *ServerConn) IsTimePreciseInList() bool {
  667. return c.mlstSupported
  668. }
  669. // ChangeDir issues a CWD FTP command, which changes the current directory to
  670. // the specified path.
  671. func (c *ServerConn) ChangeDir(path string) error {
  672. _, _, err := c.cmd(StatusRequestedFileActionOK, "CWD %s", path)
  673. return err
  674. }
  675. // ChangeDirToParent issues a CDUP FTP command, which changes the current
  676. // directory to the parent directory. This is similar to a call to ChangeDir
  677. // with a path set to "..".
  678. func (c *ServerConn) ChangeDirToParent() error {
  679. _, _, err := c.cmd(StatusRequestedFileActionOK, "CDUP")
  680. return err
  681. }
  682. // CurrentDir issues a PWD FTP command, which Returns the path of the current
  683. // directory.
  684. func (c *ServerConn) CurrentDir() (string, error) {
  685. _, msg, err := c.cmd(StatusPathCreated, "PWD")
  686. if err != nil {
  687. return "", err
  688. }
  689. start := strings.Index(msg, "\"")
  690. end := strings.LastIndex(msg, "\"")
  691. if start == -1 || end == -1 {
  692. return "", errors.New("unsuported PWD response format")
  693. }
  694. return msg[start+1 : end], nil
  695. }
  696. // FileSize issues a SIZE FTP command, which Returns the size of the file
  697. func (c *ServerConn) FileSize(path string) (int64, error) {
  698. _, msg, err := c.cmd(StatusFile, "SIZE %s", path)
  699. if err != nil {
  700. return 0, err
  701. }
  702. return strconv.ParseInt(msg, 10, 64)
  703. }
  704. // GetTime issues the MDTM FTP command to obtain the file modification time.
  705. // It returns a UTC time.
  706. func (c *ServerConn) GetTime(path string) (time.Time, error) {
  707. var t time.Time
  708. if !c.mdtmSupported {
  709. return t, errors.New("GetTime is not supported")
  710. }
  711. _, msg, err := c.cmd(StatusFile, "MDTM %s", path)
  712. if err != nil {
  713. return t, err
  714. }
  715. return time.ParseInLocation(timeFormat, msg, time.UTC)
  716. }
  717. // IsGetTimeSupported allows library callers to check in advance that they
  718. // can use GetTime to get file time.
  719. func (c *ServerConn) IsGetTimeSupported() bool {
  720. return c.mdtmSupported
  721. }
  722. // SetTime issues the MFMT FTP command to set the file modification time.
  723. // Also it can use a non-standard form of the MDTM command supported by
  724. // the VsFtpd server instead of MFMT for the same purpose.
  725. // See "mdtm_write" in https://security.appspot.com/vsftpd/vsftpd_conf.html
  726. func (c *ServerConn) SetTime(path string, t time.Time) (err error) {
  727. utime := t.In(time.UTC).Format(timeFormat)
  728. switch {
  729. case c.mfmtSupported:
  730. _, _, err = c.cmd(StatusFile, "MFMT %s %s", utime, path)
  731. case c.mdtmCanWrite:
  732. _, _, err = c.cmd(StatusFile, "MDTM %s %s", utime, path)
  733. default:
  734. err = errors.New("SetTime is not supported")
  735. }
  736. return
  737. }
  738. // IsSetTimeSupported allows library callers to check in advance that they
  739. // can use SetTime to set file time.
  740. func (c *ServerConn) IsSetTimeSupported() bool {
  741. return c.mfmtSupported || c.mdtmCanWrite
  742. }
  743. // Retr issues a RETR FTP command to fetch the specified file from the remote
  744. // FTP server.
  745. //
  746. // The returned ReadCloser must be closed to cleanup the FTP data connection.
  747. func (c *ServerConn) Retr(path string) (*Response, error) {
  748. return c.RetrFrom(path, 0)
  749. }
  750. // RetrFrom issues a RETR FTP command to fetch the specified file from the remote
  751. // FTP server, the server will not send the offset first bytes of the file.
  752. //
  753. // The returned ReadCloser must be closed to cleanup the FTP data connection.
  754. func (c *ServerConn) RetrFrom(path string, offset uint64) (*Response, error) {
  755. conn, err := c.cmdDataConnFrom(offset, "RETR %s", path)
  756. if err != nil {
  757. return nil, err
  758. }
  759. return &Response{conn: conn, c: c}, nil
  760. }
  761. // Stor issues a STOR FTP command to store a file to the remote FTP server.
  762. // Stor creates the specified file with the content of the io.Reader.
  763. //
  764. // Hint: io.Pipe() can be used if an io.Writer is required.
  765. func (c *ServerConn) Stor(path string, r io.Reader) error {
  766. return c.StorFrom(path, r, 0)
  767. }
  768. // checkDataShut reads the "closing data connection" status from the
  769. // control connection. It is called after transferring a piece of data
  770. // on the data connection during which the control connection was idle.
  771. // This may result in the idle timeout triggering on the control connection
  772. // right when we try to read the response.
  773. // The ShutTimeout dial option will rescue here. It will nudge the control
  774. // connection deadline right before checking the data closing status.
  775. func (c *ServerConn) checkDataShut() error {
  776. if c.options.shutTimeout != 0 {
  777. shutDeadline := time.Now().Add(c.options.shutTimeout)
  778. if err := c.netConn.SetDeadline(shutDeadline); err != nil {
  779. return err
  780. }
  781. }
  782. _, _, err := c.conn.ReadResponse(StatusClosingDataConnection)
  783. return err
  784. }
  785. // StorFrom issues a STOR FTP command to store a file to the remote FTP server.
  786. // Stor creates the specified file with the content of the io.Reader, writing
  787. // on the server will start at the given file offset.
  788. //
  789. // Hint: io.Pipe() can be used if an io.Writer is required.
  790. func (c *ServerConn) StorFrom(path string, r io.Reader, offset uint64) error {
  791. conn, err := c.cmdDataConnFrom(offset, "STOR %s", path)
  792. if err != nil {
  793. return err
  794. }
  795. var errs *multierror.Error
  796. // if the upload fails we still need to try to read the server
  797. // response otherwise if the failure is not due to a connection problem,
  798. // for example the server denied the upload for quota limits, we miss
  799. // the response and we cannot use the connection to send other commands.
  800. if n, err := io.Copy(conn, r); err != nil {
  801. errs = multierror.Append(errs, err)
  802. } else if n == 0 {
  803. // If we wrote no bytes and got no error, make sure we call
  804. // tls.Handshake on the connection as it won't get called
  805. // unless Write() is called. (See comment in openDataConn()).
  806. //
  807. // ProFTP doesn't like this and returns "Unable to build data
  808. // connection: Operation not permitted" when trying to upload
  809. // an empty file without this.
  810. if do, ok := conn.(interface{ Handshake() error }); ok {
  811. if err := do.Handshake(); err != nil {
  812. errs = multierror.Append(errs, err)
  813. }
  814. }
  815. }
  816. if err := conn.Close(); err != nil {
  817. errs = multierror.Append(errs, err)
  818. }
  819. if err := c.checkDataShut(); err != nil {
  820. errs = multierror.Append(errs, err)
  821. }
  822. return errs.ErrorOrNil()
  823. }
  824. // Append issues a APPE FTP command to store a file to the remote FTP server.
  825. // If a file already exists with the given path, then the content of the
  826. // io.Reader is appended. Otherwise, a new file is created with that content.
  827. //
  828. // Hint: io.Pipe() can be used if an io.Writer is required.
  829. func (c *ServerConn) Append(path string, r io.Reader) error {
  830. conn, err := c.cmdDataConnFrom(0, "APPE %s", path)
  831. if err != nil {
  832. return err
  833. }
  834. var errs *multierror.Error
  835. if _, err := io.Copy(conn, r); err != nil {
  836. errs = multierror.Append(errs, err)
  837. }
  838. if err := conn.Close(); err != nil {
  839. errs = multierror.Append(errs, err)
  840. }
  841. if err := c.checkDataShut(); err != nil {
  842. errs = multierror.Append(errs, err)
  843. }
  844. return errs.ErrorOrNil()
  845. }
  846. // Rename renames a file on the remote FTP server.
  847. func (c *ServerConn) Rename(from, to string) error {
  848. _, _, err := c.cmd(StatusRequestFilePending, "RNFR %s", from)
  849. if err != nil {
  850. return err
  851. }
  852. _, _, err = c.cmd(StatusRequestedFileActionOK, "RNTO %s", to)
  853. return err
  854. }
  855. // Delete issues a DELE FTP command to delete the specified file from the
  856. // remote FTP server.
  857. func (c *ServerConn) Delete(path string) error {
  858. _, _, err := c.cmd(StatusRequestedFileActionOK, "DELE %s", path)
  859. return err
  860. }
  861. // RemoveDirRecur deletes a non-empty folder recursively using
  862. // RemoveDir and Delete
  863. func (c *ServerConn) RemoveDirRecur(path string) error {
  864. err := c.ChangeDir(path)
  865. if err != nil {
  866. return err
  867. }
  868. currentDir, err := c.CurrentDir()
  869. if err != nil {
  870. return err
  871. }
  872. entries, err := c.List(currentDir)
  873. if err != nil {
  874. return err
  875. }
  876. for _, entry := range entries {
  877. if entry.Name != ".." && entry.Name != "." {
  878. if entry.Type == EntryTypeFolder {
  879. err = c.RemoveDirRecur(currentDir + "/" + entry.Name)
  880. if err != nil {
  881. return err
  882. }
  883. } else {
  884. err = c.Delete(entry.Name)
  885. if err != nil {
  886. return err
  887. }
  888. }
  889. }
  890. }
  891. err = c.ChangeDirToParent()
  892. if err != nil {
  893. return err
  894. }
  895. err = c.RemoveDir(currentDir)
  896. return err
  897. }
  898. // MakeDir issues a MKD FTP command to create the specified directory on the
  899. // remote FTP server.
  900. func (c *ServerConn) MakeDir(path string) error {
  901. _, _, err := c.cmd(StatusPathCreated, "MKD %s", path)
  902. return err
  903. }
  904. // RemoveDir issues a RMD FTP command to remove the specified directory from
  905. // the remote FTP server.
  906. func (c *ServerConn) RemoveDir(path string) error {
  907. _, _, err := c.cmd(StatusRequestedFileActionOK, "RMD %s", path)
  908. return err
  909. }
  910. // Walk prepares the internal walk function so that the caller can begin traversing the directory
  911. func (c *ServerConn) Walk(root string) *Walker {
  912. w := new(Walker)
  913. w.serverConn = c
  914. if !strings.HasSuffix(root, "/") {
  915. root += "/"
  916. }
  917. w.root = root
  918. w.descend = true
  919. return w
  920. }
  921. // NoOp issues a NOOP FTP command.
  922. // NOOP has no effects and is usually used to prevent the remote FTP server to
  923. // close the otherwise idle connection.
  924. func (c *ServerConn) NoOp() error {
  925. _, _, err := c.cmd(StatusCommandOK, "NOOP")
  926. return err
  927. }
  928. // Logout issues a REIN FTP command to logout the current user.
  929. func (c *ServerConn) Logout() error {
  930. _, _, err := c.cmd(StatusReady, "REIN")
  931. return err
  932. }
  933. // Quit issues a QUIT FTP command to properly close the connection from the
  934. // remote FTP server.
  935. func (c *ServerConn) Quit() error {
  936. var errs *multierror.Error
  937. if _, err := c.conn.Cmd("QUIT"); err != nil {
  938. errs = multierror.Append(errs, err)
  939. }
  940. if err := c.conn.Close(); err != nil {
  941. errs = multierror.Append(errs, err)
  942. }
  943. return errs.ErrorOrNil()
  944. }
  945. // Read implements the io.Reader interface on a FTP data connection.
  946. func (r *Response) Read(buf []byte) (int, error) {
  947. return r.conn.Read(buf)
  948. }
  949. // Close implements the io.Closer interface on a FTP data connection.
  950. // After the first call, Close will do nothing and return nil.
  951. func (r *Response) Close() error {
  952. if r.closed {
  953. return nil
  954. }
  955. var errs *multierror.Error
  956. if err := r.conn.Close(); err != nil {
  957. errs = multierror.Append(errs, err)
  958. }
  959. if err := r.c.checkDataShut(); err != nil {
  960. errs = multierror.Append(errs, err)
  961. }
  962. r.closed = true
  963. return errs.ErrorOrNil()
  964. }
  965. // SetDeadline sets the deadlines associated with the connection.
  966. func (r *Response) SetDeadline(t time.Time) error {
  967. return r.conn.SetDeadline(t)
  968. }
  969. // String returns the string representation of EntryType t.
  970. func (t EntryType) String() string {
  971. return [...]string{"file", "folder", "link"}[t]
  972. }