ftp.go 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811
  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. )
  17. // EntryType describes the different types of an Entry.
  18. type EntryType int
  19. // The differents types of an Entry
  20. const (
  21. EntryTypeFile EntryType = iota
  22. EntryTypeFolder
  23. EntryTypeLink
  24. )
  25. // ServerConn represents the connection to a remote FTP server.
  26. // A single connection only supports one in-flight data connection.
  27. // It is not safe to be called concurrently.
  28. type ServerConn struct {
  29. options *dialOptions
  30. conn *textproto.Conn
  31. host string
  32. // Server capabilities discovered at runtime
  33. features map[string]string
  34. skipEPSV bool
  35. mlstSupported bool
  36. }
  37. // DialOption represents an option to start a new connection with Dial
  38. type DialOption struct {
  39. setup func(do *dialOptions)
  40. }
  41. // dialOptions contains all the options set by DialOption.setup
  42. type dialOptions struct {
  43. context context.Context
  44. dialer net.Dialer
  45. tlsConfig *tls.Config
  46. explicitTLS bool
  47. conn net.Conn
  48. disableEPSV bool
  49. location *time.Location
  50. debugOutput io.Writer
  51. dialFunc func(network, address string) (net.Conn, error)
  52. }
  53. // Entry describes a file and is returned by List().
  54. type Entry struct {
  55. Name string
  56. Target string // target of symbolic link
  57. Type EntryType
  58. Size uint64
  59. Time time.Time
  60. }
  61. // Response represents a data-connection
  62. type Response struct {
  63. conn net.Conn
  64. c *ServerConn
  65. closed bool
  66. }
  67. // Dial connects to the specified address with optional options
  68. func Dial(addr string, options ...DialOption) (*ServerConn, error) {
  69. do := &dialOptions{}
  70. for _, option := range options {
  71. option.setup(do)
  72. }
  73. if do.location == nil {
  74. do.location = time.UTC
  75. }
  76. tconn := do.conn
  77. if tconn == nil {
  78. var err error
  79. if do.dialFunc != nil {
  80. tconn, err = do.dialFunc("tcp", addr)
  81. } else if do.tlsConfig != nil && !do.explicitTLS {
  82. tconn, err = tls.DialWithDialer(&do.dialer, "tcp", addr, do.tlsConfig)
  83. } else {
  84. ctx := do.context
  85. if ctx == nil {
  86. ctx = context.Background()
  87. }
  88. tconn, err = do.dialer.DialContext(ctx, "tcp", addr)
  89. }
  90. if err != nil {
  91. return nil, err
  92. }
  93. }
  94. // Use the resolved IP address in case addr contains a domain name
  95. // If we use the domain name, we might not resolve to the same IP.
  96. remoteAddr := tconn.RemoteAddr().(*net.TCPAddr)
  97. c := &ServerConn{
  98. options: do,
  99. features: make(map[string]string),
  100. conn: textproto.NewConn(do.wrapConn(tconn)),
  101. host: remoteAddr.IP.String(),
  102. }
  103. _, _, err := c.conn.ReadResponse(StatusReady)
  104. if err != nil {
  105. c.Quit()
  106. return nil, err
  107. }
  108. if do.explicitTLS {
  109. if err := c.authTLS(); err != nil {
  110. _ = c.Quit()
  111. return nil, err
  112. }
  113. tconn = tls.Client(tconn, do.tlsConfig)
  114. c.conn = textproto.NewConn(do.wrapConn(tconn))
  115. }
  116. err = c.feat()
  117. if err != nil {
  118. c.Quit()
  119. return nil, err
  120. }
  121. if _, mlstSupported := c.features["MLST"]; mlstSupported {
  122. c.mlstSupported = true
  123. }
  124. return c, nil
  125. }
  126. // DialWithTimeout returns a DialOption that configures the ServerConn with specified timeout
  127. func DialWithTimeout(timeout time.Duration) DialOption {
  128. return DialOption{func(do *dialOptions) {
  129. do.dialer.Timeout = timeout
  130. }}
  131. }
  132. // DialWithDialer returns a DialOption that configures the ServerConn with specified net.Dialer
  133. func DialWithDialer(dialer net.Dialer) DialOption {
  134. return DialOption{func(do *dialOptions) {
  135. do.dialer = dialer
  136. }}
  137. }
  138. // DialWithNetConn returns a DialOption that configures the ServerConn with the underlying net.Conn
  139. func DialWithNetConn(conn net.Conn) DialOption {
  140. return DialOption{func(do *dialOptions) {
  141. do.conn = conn
  142. }}
  143. }
  144. // DialWithDisabledEPSV returns a DialOption that configures the ServerConn with EPSV disabled
  145. // Note that EPSV is only used when advertised in the server features.
  146. func DialWithDisabledEPSV(disabled bool) DialOption {
  147. return DialOption{func(do *dialOptions) {
  148. do.disableEPSV = disabled
  149. }}
  150. }
  151. // DialWithLocation returns a DialOption that configures the ServerConn with specified time.Location
  152. // The location is used to parse the dates sent by the server which are in server's timezone
  153. func DialWithLocation(location *time.Location) DialOption {
  154. return DialOption{func(do *dialOptions) {
  155. do.location = location
  156. }}
  157. }
  158. // DialWithContext returns a DialOption that configures the ServerConn with specified context
  159. // The context will be used for the initial connection setup
  160. func DialWithContext(ctx context.Context) DialOption {
  161. return DialOption{func(do *dialOptions) {
  162. do.context = ctx
  163. }}
  164. }
  165. // DialWithTLS returns a DialOption that configures the ServerConn with specified TLS config
  166. //
  167. // If called together with the DialWithDialFunc option, the DialWithDialFunc function
  168. // will be used when dialing new connections but regardless of the function,
  169. // the connection will be treated as a TLS connection.
  170. func DialWithTLS(tlsConfig *tls.Config) DialOption {
  171. return DialOption{func(do *dialOptions) {
  172. do.tlsConfig = tlsConfig
  173. }}
  174. }
  175. // DialWithExplicitTLS returns a DialOption that configures the ServerConn to be upgraded to TLS
  176. // See DialWithTLS for general TLS documentation
  177. func DialWithExplicitTLS(tlsConfig *tls.Config) DialOption {
  178. return DialOption{func(do *dialOptions) {
  179. do.explicitTLS = true
  180. do.tlsConfig = tlsConfig
  181. }}
  182. }
  183. // DialWithDebugOutput returns a DialOption that configures the ServerConn to write to the Writer
  184. // everything it reads from the server
  185. func DialWithDebugOutput(w io.Writer) DialOption {
  186. return DialOption{func(do *dialOptions) {
  187. do.debugOutput = w
  188. }}
  189. }
  190. // DialWithDialFunc returns a DialOption that configures the ServerConn to use the
  191. // specified function to establish both control and data connections
  192. //
  193. // If used together with the DialWithNetConn option, the DialWithNetConn
  194. // takes precedence for the control connection, while data connections will
  195. // be established using function specified with the DialWithDialFunc option
  196. func DialWithDialFunc(f func(network, address string) (net.Conn, error)) DialOption {
  197. return DialOption{func(do *dialOptions) {
  198. do.dialFunc = f
  199. }}
  200. }
  201. func (o *dialOptions) wrapConn(netConn net.Conn) io.ReadWriteCloser {
  202. if o.debugOutput == nil {
  203. return netConn
  204. }
  205. return newDebugWrapper(netConn, o.debugOutput)
  206. }
  207. // Connect is an alias to Dial, for backward compatibility
  208. func Connect(addr string) (*ServerConn, error) {
  209. return Dial(addr)
  210. }
  211. // DialTimeout initializes the connection to the specified ftp server address.
  212. //
  213. // It is generally followed by a call to Login() as most FTP commands require
  214. // an authenticated user.
  215. func DialTimeout(addr string, timeout time.Duration) (*ServerConn, error) {
  216. return Dial(addr, DialWithTimeout(timeout))
  217. }
  218. // Login authenticates the client with specified user and password.
  219. //
  220. // "anonymous"/"anonymous" is a common user/password scheme for FTP servers
  221. // that allows anonymous read-only accounts.
  222. func (c *ServerConn) Login(user, password string) error {
  223. code, message, err := c.cmd(-1, "USER %s", user)
  224. if err != nil {
  225. return err
  226. }
  227. switch code {
  228. case StatusLoggedIn:
  229. case StatusUserOK:
  230. _, _, err = c.cmd(StatusLoggedIn, "PASS %s", password)
  231. if err != nil {
  232. return err
  233. }
  234. default:
  235. return errors.New(message)
  236. }
  237. // Switch to binary mode
  238. if _, _, err = c.cmd(StatusCommandOK, "TYPE I"); err != nil {
  239. return err
  240. }
  241. // Switch to UTF-8
  242. err = c.setUTF8()
  243. // If using implicit TLS, make data connections also use TLS
  244. if c.options.tlsConfig != nil {
  245. c.cmd(StatusCommandOK, "PBSZ 0")
  246. c.cmd(StatusCommandOK, "PROT P")
  247. }
  248. return err
  249. }
  250. // authTLS upgrades the connection to use TLS
  251. func (c *ServerConn) authTLS() error {
  252. _, _, err := c.cmd(StatusAuthOK, "AUTH TLS")
  253. return err
  254. }
  255. // feat issues a FEAT FTP command to list the additional commands supported by
  256. // the remote FTP server.
  257. // FEAT is described in RFC 2389
  258. func (c *ServerConn) feat() error {
  259. code, message, err := c.cmd(-1, "FEAT")
  260. if err != nil {
  261. return err
  262. }
  263. if code != StatusSystem {
  264. // The server does not support the FEAT command. This is not an
  265. // error: we consider that there is no additional feature.
  266. return nil
  267. }
  268. lines := strings.Split(message, "\n")
  269. for _, line := range lines {
  270. if !strings.HasPrefix(line, " ") {
  271. continue
  272. }
  273. line = strings.TrimSpace(line)
  274. featureElements := strings.SplitN(line, " ", 2)
  275. command := featureElements[0]
  276. var commandDesc string
  277. if len(featureElements) == 2 {
  278. commandDesc = featureElements[1]
  279. }
  280. c.features[command] = commandDesc
  281. }
  282. return nil
  283. }
  284. // setUTF8 issues an "OPTS UTF8 ON" command.
  285. func (c *ServerConn) setUTF8() error {
  286. if _, ok := c.features["UTF8"]; !ok {
  287. return nil
  288. }
  289. code, message, err := c.cmd(-1, "OPTS UTF8 ON")
  290. if err != nil {
  291. return err
  292. }
  293. // Workaround for FTP servers, that does not support this option.
  294. if code == StatusBadArguments {
  295. return nil
  296. }
  297. // The ftpd "filezilla-server" has FEAT support for UTF8, but always returns
  298. // "202 UTF8 mode is always enabled. No need to send this command." when
  299. // trying to use it. That's OK
  300. if code == StatusCommandNotImplemented {
  301. return nil
  302. }
  303. if code != StatusCommandOK {
  304. return errors.New(message)
  305. }
  306. return nil
  307. }
  308. // epsv issues an "EPSV" command to get a port number for a data connection.
  309. func (c *ServerConn) epsv() (port int, err error) {
  310. _, line, err := c.cmd(StatusExtendedPassiveMode, "EPSV")
  311. if err != nil {
  312. return
  313. }
  314. start := strings.Index(line, "|||")
  315. end := strings.LastIndex(line, "|")
  316. if start == -1 || end == -1 {
  317. err = errors.New("invalid EPSV response format")
  318. return
  319. }
  320. port, err = strconv.Atoi(line[start+3 : end])
  321. return
  322. }
  323. // pasv issues a "PASV" command to get a port number for a data connection.
  324. func (c *ServerConn) pasv() (host string, port int, err error) {
  325. _, line, err := c.cmd(StatusPassiveMode, "PASV")
  326. if err != nil {
  327. return
  328. }
  329. // PASV response format : 227 Entering Passive Mode (h1,h2,h3,h4,p1,p2).
  330. start := strings.Index(line, "(")
  331. end := strings.LastIndex(line, ")")
  332. if start == -1 || end == -1 {
  333. err = errors.New("invalid PASV response format")
  334. return
  335. }
  336. // We have to split the response string
  337. pasvData := strings.Split(line[start+1:end], ",")
  338. if len(pasvData) < 6 {
  339. err = errors.New("invalid PASV response format")
  340. return
  341. }
  342. // Let's compute the port number
  343. portPart1, err1 := strconv.Atoi(pasvData[4])
  344. if err1 != nil {
  345. err = err1
  346. return
  347. }
  348. portPart2, err2 := strconv.Atoi(pasvData[5])
  349. if err2 != nil {
  350. err = err2
  351. return
  352. }
  353. // Recompose port
  354. port = portPart1*256 + portPart2
  355. // Make the IP address to connect to
  356. host = strings.Join(pasvData[0:4], ".")
  357. return
  358. }
  359. // getDataConnPort returns a host, port for a new data connection
  360. // it uses the best available method to do so
  361. func (c *ServerConn) getDataConnPort() (string, int, error) {
  362. if !c.options.disableEPSV && !c.skipEPSV {
  363. if port, err := c.epsv(); err == nil {
  364. return c.host, port, nil
  365. }
  366. // if there is an error, skip EPSV for the next attempts
  367. c.skipEPSV = true
  368. }
  369. return c.pasv()
  370. }
  371. // openDataConn creates a new FTP data connection.
  372. func (c *ServerConn) openDataConn() (net.Conn, error) {
  373. host, port, err := c.getDataConnPort()
  374. if err != nil {
  375. return nil, err
  376. }
  377. addr := net.JoinHostPort(host, strconv.Itoa(port))
  378. if c.options.dialFunc != nil {
  379. return c.options.dialFunc("tcp", addr)
  380. }
  381. if c.options.tlsConfig != nil {
  382. conn, err := c.options.dialer.Dial("tcp", addr)
  383. if err != nil {
  384. return nil, err
  385. }
  386. return tls.Client(conn, c.options.tlsConfig), err
  387. }
  388. return c.options.dialer.Dial("tcp", addr)
  389. }
  390. // cmd is a helper function to execute a command and check for the expected FTP
  391. // return code
  392. func (c *ServerConn) cmd(expected int, format string, args ...interface{}) (int, string, error) {
  393. _, err := c.conn.Cmd(format, args...)
  394. if err != nil {
  395. return 0, "", err
  396. }
  397. return c.conn.ReadResponse(expected)
  398. }
  399. // cmdDataConnFrom executes a command which require a FTP data connection.
  400. // Issues a REST FTP command to specify the number of bytes to skip for the transfer.
  401. func (c *ServerConn) cmdDataConnFrom(offset uint64, format string, args ...interface{}) (net.Conn, error) {
  402. conn, err := c.openDataConn()
  403. if err != nil {
  404. return nil, err
  405. }
  406. if offset != 0 {
  407. _, _, err := c.cmd(StatusRequestFilePending, "REST %d", offset)
  408. if err != nil {
  409. conn.Close()
  410. return nil, err
  411. }
  412. }
  413. _, err = c.conn.Cmd(format, args...)
  414. if err != nil {
  415. conn.Close()
  416. return nil, err
  417. }
  418. code, msg, err := c.conn.ReadResponse(-1)
  419. if err != nil {
  420. conn.Close()
  421. return nil, err
  422. }
  423. if code != StatusAlreadyOpen && code != StatusAboutToSend {
  424. conn.Close()
  425. return nil, &textproto.Error{Code: code, Msg: msg}
  426. }
  427. return conn, nil
  428. }
  429. // NameList issues an NLST FTP command.
  430. func (c *ServerConn) NameList(path string) (entries []string, err error) {
  431. conn, err := c.cmdDataConnFrom(0, "NLST %s", path)
  432. if err != nil {
  433. return
  434. }
  435. r := &Response{conn: conn, c: c}
  436. defer r.Close()
  437. scanner := bufio.NewScanner(r)
  438. for scanner.Scan() {
  439. entries = append(entries, scanner.Text())
  440. }
  441. if err = scanner.Err(); err != nil {
  442. return entries, err
  443. }
  444. return
  445. }
  446. // List issues a LIST FTP command.
  447. func (c *ServerConn) List(path string) (entries []*Entry, err error) {
  448. var cmd string
  449. var parser parseFunc
  450. if c.mlstSupported {
  451. cmd = "MLSD"
  452. parser = parseRFC3659ListLine
  453. } else {
  454. cmd = "LIST"
  455. parser = parseListLine
  456. }
  457. conn, err := c.cmdDataConnFrom(0, "%s %s", cmd, path)
  458. if err != nil {
  459. return
  460. }
  461. r := &Response{conn: conn, c: c}
  462. defer r.Close()
  463. scanner := bufio.NewScanner(r)
  464. now := time.Now()
  465. for scanner.Scan() {
  466. entry, err := parser(scanner.Text(), now, c.options.location)
  467. if err == nil {
  468. entries = append(entries, entry)
  469. }
  470. }
  471. if err := scanner.Err(); err != nil {
  472. return nil, err
  473. }
  474. return
  475. }
  476. // ChangeDir issues a CWD FTP command, which changes the current directory to
  477. // the specified path.
  478. func (c *ServerConn) ChangeDir(path string) error {
  479. _, _, err := c.cmd(StatusRequestedFileActionOK, "CWD %s", path)
  480. return err
  481. }
  482. // ChangeDirToParent issues a CDUP FTP command, which changes the current
  483. // directory to the parent directory. This is similar to a call to ChangeDir
  484. // with a path set to "..".
  485. func (c *ServerConn) ChangeDirToParent() error {
  486. _, _, err := c.cmd(StatusRequestedFileActionOK, "CDUP")
  487. return err
  488. }
  489. // CurrentDir issues a PWD FTP command, which Returns the path of the current
  490. // directory.
  491. func (c *ServerConn) CurrentDir() (string, error) {
  492. _, msg, err := c.cmd(StatusPathCreated, "PWD")
  493. if err != nil {
  494. return "", err
  495. }
  496. start := strings.Index(msg, "\"")
  497. end := strings.LastIndex(msg, "\"")
  498. if start == -1 || end == -1 {
  499. return "", errors.New("unsuported PWD response format")
  500. }
  501. return msg[start+1 : end], nil
  502. }
  503. // FileSize issues a SIZE FTP command, which Returns the size of the file
  504. func (c *ServerConn) FileSize(path string) (int64, error) {
  505. _, msg, err := c.cmd(StatusFile, "SIZE %s", path)
  506. if err != nil {
  507. return 0, err
  508. }
  509. return strconv.ParseInt(msg, 10, 64)
  510. }
  511. // Retr issues a RETR FTP command to fetch the specified file from the remote
  512. // FTP server.
  513. //
  514. // The returned ReadCloser must be closed to cleanup the FTP data connection.
  515. func (c *ServerConn) Retr(path string) (*Response, error) {
  516. return c.RetrFrom(path, 0)
  517. }
  518. // RetrFrom issues a RETR FTP command to fetch the specified file from the remote
  519. // FTP server, the server will not send the offset first bytes of the file.
  520. //
  521. // The returned ReadCloser must be closed to cleanup the FTP data connection.
  522. func (c *ServerConn) RetrFrom(path string, offset uint64) (*Response, error) {
  523. conn, err := c.cmdDataConnFrom(offset, "RETR %s", path)
  524. if err != nil {
  525. return nil, err
  526. }
  527. return &Response{conn: conn, c: c}, nil
  528. }
  529. // Stor issues a STOR FTP command to store a file to the remote FTP server.
  530. // Stor creates the specified file with the content of the io.Reader.
  531. //
  532. // Hint: io.Pipe() can be used if an io.Writer is required.
  533. func (c *ServerConn) Stor(path string, r io.Reader) error {
  534. return c.StorFrom(path, r, 0)
  535. }
  536. // StorFrom issues a STOR FTP command to store a file to the remote FTP server.
  537. // Stor creates the specified file with the content of the io.Reader, writing
  538. // on the server will start at the given file offset.
  539. //
  540. // Hint: io.Pipe() can be used if an io.Writer is required.
  541. func (c *ServerConn) StorFrom(path string, r io.Reader, offset uint64) error {
  542. conn, err := c.cmdDataConnFrom(offset, "STOR %s", path)
  543. if err != nil {
  544. return err
  545. }
  546. _, err = io.Copy(conn, r)
  547. conn.Close()
  548. if err != nil {
  549. return err
  550. }
  551. _, _, err = c.conn.ReadResponse(StatusClosingDataConnection)
  552. return err
  553. }
  554. // Append issues a APPE FTP command to store a file to the remote FTP server.
  555. // If a file already exists with the given path, then the content of the
  556. // io.Reader is appended. Otherwise, a new file is created with that content.
  557. //
  558. // Hint: io.Pipe() can be used if an io.Writer is required.
  559. func (c *ServerConn) Append(path string, r io.Reader) error {
  560. conn, err := c.cmdDataConnFrom(0, "APPE %s", path)
  561. if err != nil {
  562. return err
  563. }
  564. _, err = io.Copy(conn, r)
  565. conn.Close()
  566. if err != nil {
  567. return err
  568. }
  569. _, _, err = c.conn.ReadResponse(StatusClosingDataConnection)
  570. return err
  571. }
  572. // Rename renames a file on the remote FTP server.
  573. func (c *ServerConn) Rename(from, to string) error {
  574. _, _, err := c.cmd(StatusRequestFilePending, "RNFR %s", from)
  575. if err != nil {
  576. return err
  577. }
  578. _, _, err = c.cmd(StatusRequestedFileActionOK, "RNTO %s", to)
  579. return err
  580. }
  581. // Delete issues a DELE FTP command to delete the specified file from the
  582. // remote FTP server.
  583. func (c *ServerConn) Delete(path string) error {
  584. _, _, err := c.cmd(StatusRequestedFileActionOK, "DELE %s", path)
  585. return err
  586. }
  587. // RemoveDirRecur deletes a non-empty folder recursively using
  588. // RemoveDir and Delete
  589. func (c *ServerConn) RemoveDirRecur(path string) error {
  590. err := c.ChangeDir(path)
  591. if err != nil {
  592. return err
  593. }
  594. currentDir, err := c.CurrentDir()
  595. if err != nil {
  596. return err
  597. }
  598. entries, err := c.List(currentDir)
  599. if err != nil {
  600. return err
  601. }
  602. for _, entry := range entries {
  603. if entry.Name != ".." && entry.Name != "." {
  604. if entry.Type == EntryTypeFolder {
  605. err = c.RemoveDirRecur(currentDir + "/" + entry.Name)
  606. if err != nil {
  607. return err
  608. }
  609. } else {
  610. err = c.Delete(entry.Name)
  611. if err != nil {
  612. return err
  613. }
  614. }
  615. }
  616. }
  617. err = c.ChangeDirToParent()
  618. if err != nil {
  619. return err
  620. }
  621. err = c.RemoveDir(currentDir)
  622. return err
  623. }
  624. // MakeDir issues a MKD FTP command to create the specified directory on the
  625. // remote FTP server.
  626. func (c *ServerConn) MakeDir(path string) error {
  627. _, _, err := c.cmd(StatusPathCreated, "MKD %s", path)
  628. return err
  629. }
  630. // RemoveDir issues a RMD FTP command to remove the specified directory from
  631. // the remote FTP server.
  632. func (c *ServerConn) RemoveDir(path string) error {
  633. _, _, err := c.cmd(StatusRequestedFileActionOK, "RMD %s", path)
  634. return err
  635. }
  636. //Walk prepares the internal walk function so that the caller can begin traversing the directory
  637. func (c *ServerConn) Walk(root string) *Walker {
  638. w := new(Walker)
  639. w.serverConn = c
  640. if !strings.HasSuffix(root, "/") {
  641. root += "/"
  642. }
  643. w.root = root
  644. return w
  645. }
  646. // NoOp issues a NOOP FTP command.
  647. // NOOP has no effects and is usually used to prevent the remote FTP server to
  648. // close the otherwise idle connection.
  649. func (c *ServerConn) NoOp() error {
  650. _, _, err := c.cmd(StatusCommandOK, "NOOP")
  651. return err
  652. }
  653. // Logout issues a REIN FTP command to logout the current user.
  654. func (c *ServerConn) Logout() error {
  655. _, _, err := c.cmd(StatusReady, "REIN")
  656. return err
  657. }
  658. // Quit issues a QUIT FTP command to properly close the connection from the
  659. // remote FTP server.
  660. func (c *ServerConn) Quit() error {
  661. c.conn.Cmd("QUIT")
  662. return c.conn.Close()
  663. }
  664. // Read implements the io.Reader interface on a FTP data connection.
  665. func (r *Response) Read(buf []byte) (int, error) {
  666. return r.conn.Read(buf)
  667. }
  668. // Close implements the io.Closer interface on a FTP data connection.
  669. // After the first call, Close will do nothing and return nil.
  670. func (r *Response) Close() error {
  671. if r.closed {
  672. return nil
  673. }
  674. err := r.conn.Close()
  675. _, _, err2 := r.c.conn.ReadResponse(StatusClosingDataConnection)
  676. if err2 != nil {
  677. err = err2
  678. }
  679. r.closed = true
  680. return err
  681. }
  682. // SetDeadline sets the deadlines associated with the connection.
  683. func (r *Response) SetDeadline(t time.Time) error {
  684. return r.conn.SetDeadline(t)
  685. }