Codebase list golang-github-jlaffaye-ftp / ee5d4fe
New upstream snapshot. Debian Janitor 2 years ago
10 changed file(s) with 211 addition(s) and 97 deletion(s). Raw diff Collapse all Expand all
22 [![Build Status](https://travis-ci.org/jlaffaye/ftp.svg?branch=master)](https://travis-ci.org/jlaffaye/ftp)
33 [![Coverage Status](https://coveralls.io/repos/jlaffaye/ftp/badge.svg?branch=master&service=github)](https://coveralls.io/github/jlaffaye/ftp?branch=master)
44 [![Go ReportCard](http://goreportcard.com/badge/jlaffaye/ftp)](http://goreportcard.com/report/jlaffaye/ftp)
5 [![godoc.org](https://godoc.org/github.com/jlaffaye/ftp?status.svg)](http://godoc.org/github.com/jlaffaye/ftp)
5 [![Go Reference](https://pkg.go.dev/badge/github.com/jlaffaye/ftp.svg)](https://pkg.go.dev/github.com/jlaffaye/ftp)
66
77 A FTP client package for Go
88
1414
1515 ## Documentation ##
1616
17 https://pkg.go.dev/github.com/jlaffaye/ftp?tab=doc
17 https://pkg.go.dev/github.com/jlaffaye/ftp
1818
1919 ## Example ##
2020
66 "strings"
77 "testing"
88 "time"
9
10 "github.com/stretchr/testify/assert"
911 )
1012
1113 const (
7072 if err != nil {
7173 t.Error(err)
7274 } else {
73 buf, err := ioutil.ReadAll(r)
75 buf, errRead := ioutil.ReadAll(r)
7476 if err != nil {
75 t.Error(err)
77 t.Error(errRead)
7678 }
7779 if string(buf) != testData {
7880 t.Errorf("'%s'", buf)
8789 t.Error(err)
8890 } else {
8991 r.SetDeadline(time.Now())
90 _, err := ioutil.ReadAll(r)
92 _, err = ioutil.ReadAll(r)
9193 if err == nil {
9294 t.Error("deadline should have caused error")
9395 } else if !strings.HasSuffix(err.Error(), "i/o timeout") {
101103 if err != nil {
102104 t.Error(err)
103105 } else {
104 buf, err := ioutil.ReadAll(r)
105 if err != nil {
106 t.Error(err)
106 buf, errRead := ioutil.ReadAll(r)
107 if errRead != nil {
108 t.Error(errRead)
107109 }
108110 expected := testData[5:]
109111 if string(buf) != expected {
123125 if err != nil {
124126 t.Error(err)
125127 } else {
126 buf, err := ioutil.ReadAll(r)
128 buf, errRead := ioutil.ReadAll(r)
127129 if err != nil {
128 t.Error(err)
130 t.Error(errRead)
129131 }
130132 if string(buf) != testData+testData {
131133 t.Errorf("'%s'", buf)
190192 }
191193 }
192194
193 if err := c.Quit(); err != nil {
195 if err = c.Quit(); err != nil {
194196 t.Fatal(err)
195197 }
196198
227229 t.Skip("skipping test in short mode.")
228230 }
229231
230 c, err := DialTimeout("localhost:2121", 1*time.Second)
231 if err == nil {
232 if c, err := DialTimeout("localhost:2121", 1*time.Second); err == nil {
233 c.Quit()
232234 t.Fatal("expected timeout, got nil error")
233 c.Quit()
234235 }
235236 }
236237
300301 // Wait for the connection to close
301302 mock.Wait()
302303 }
304
305 func TestListCurrentDir(t *testing.T) {
306 mock, c := openConn(t, "127.0.0.1")
307
308 _, err := c.List("")
309 assert.NoError(t, err)
310 assert.Equal(t, "LIST", mock.lastFull, "LIST must not have a trailing whitespace")
311
312 _, err = c.NameList("")
313 assert.NoError(t, err)
314 assert.Equal(t, "NLST", mock.lastFull, "NLST must not have a trailing whitespace")
315
316 err = c.Quit()
317 assert.NoError(t, err)
318
319 mock.Wait()
320 }
1717 listener *net.TCPListener
1818 proto *textproto.Conn
1919 commands []string // list of received commands
20 lastFull string // full last command
2021 rest int
2122 fileCont *bytes.Buffer
2223 dataConn *mockDataConn
6566
6667 for {
6768 fullCommand, _ := mock.proto.ReadLine()
69 mock.lastFull = fullCommand
6870
6971 cmdParts := strings.Split(fullCommand, " ")
7072
202204 }
203205 if (strings.Join(cmdParts[1:], " ")) == "UTF8 ON" {
204206 mock.proto.Writer.PrintfLine("200 OK, UTF-8 enabled")
205 break
206207 }
207208 case "REIN":
208209 mock.proto.Writer.PrintfLine("220 Logged out")
327328
328329 // Helper to close a client connected to a mock server
329330 func closeConn(t *testing.T, mock *ftpMock, c *ServerConn, commands []string) {
330 expected := []string{"FEAT", "USER", "PASS", "TYPE", "OPTS"}
331 expected := []string{"USER", "PASS", "FEAT", "TYPE", "OPTS"}
331332 expected = append(expected, commands...)
332333 expected = append(expected, "QUIT")
333334
0 golang-github-jlaffaye-ftp (0.0~git20210306.5d41901-1) UNRELEASED; urgency=low
1
2 * New upstream snapshot.
3
4 -- Debian Janitor <janitor@jelmer.uk> Thu, 23 Sep 2021 14:51:51 -0000
5
06 golang-github-jlaffaye-ftp (0.0~git20200812.39e3779-1) unstable; urgency=medium
17
28 * Team upload.
+146
-66
ftp.go less more
77 "context"
88 "crypto/tls"
99 "errors"
10 "fmt"
1011 "io"
1112 "net"
1213 "net/textproto"
3738 features map[string]string
3839 skipEPSV bool
3940 mlstSupported bool
41 usePRET bool
4042 }
4143
4244 // DialOption represents an option to start a new connection with Dial
5355 conn net.Conn
5456 disableEPSV bool
5557 disableUTF8 bool
58 disableMLSD bool
5659 location *time.Location
5760 debugOutput io.Writer
5861 dialFunc func(network, address string) (net.Conn, error)
121124
122125 _, _, err := c.conn.ReadResponse(StatusReady)
123126 if err != nil {
124 c.Quit()
127 _ = c.Quit()
125128 return nil, err
126129 }
127130
134137 c.conn = textproto.NewConn(do.wrapConn(tconn))
135138 }
136139
137 err = c.feat()
138 if err != nil {
139 c.Quit()
140 return nil, err
141 }
142
143 if _, mlstSupported := c.features["MLST"]; mlstSupported {
144 c.mlstSupported = true
145 }
146
147140 return c, nil
148141 }
149142
180173 func DialWithDisabledUTF8(disabled bool) DialOption {
181174 return DialOption{func(do *dialOptions) {
182175 do.disableUTF8 = disabled
176 }}
177 }
178
179 // DialWithDisabledMLSD returns a DialOption that configures the ServerConn with MLSD option disabled
180 //
181 // This is useful for servers which advertise MLSD (eg some versions
182 // of Serv-U) but don't support it properly.
183 func DialWithDisabledMLSD(disabled bool) DialOption {
184 return DialOption{func(do *dialOptions) {
185 do.disableMLSD = disabled
183186 }}
184187 }
185188
281284 return errors.New(message)
282285 }
283286
287 // Probe features
288 err = c.feat()
289 if err != nil {
290 return err
291 }
292 if _, mlstSupported := c.features["MLST"]; mlstSupported && !c.options.disableMLSD {
293 c.mlstSupported = true
294 }
295 if _, usePRET := c.features["PRET"]; usePRET {
296 c.usePRET = true
297 }
298
284299 // Switch to binary mode
285300 if _, _, err = c.cmd(StatusCommandOK, "TYPE I"); err != nil {
286301 return err
293308
294309 // If using implicit TLS, make data connections also use TLS
295310 if c.options.tlsConfig != nil {
296 c.cmd(StatusCommandOK, "PBSZ 0")
297 c.cmd(StatusCommandOK, "PROT P")
311 if _, _, err = c.cmd(StatusCommandOK, "PBSZ 0"); err != nil {
312 return err
313 }
314 if _, _, err = c.cmd(StatusCommandOK, "PROT P"); err != nil {
315 return err
316 }
298317 }
299318
300319 return err
377396 func (c *ServerConn) epsv() (port int, err error) {
378397 _, line, err := c.cmd(StatusExtendedPassiveMode, "EPSV")
379398 if err != nil {
380 return
399 return 0, err
381400 }
382401
383402 start := strings.Index(line, "|||")
384403 end := strings.LastIndex(line, "|")
385404 if start == -1 || end == -1 {
386 err = errors.New("invalid EPSV response format")
387 return
405 return 0, errors.New("invalid EPSV response format")
388406 }
389407 port, err = strconv.Atoi(line[start+3 : end])
390 return
408 return port, err
391409 }
392410
393411 // pasv issues a "PASV" command to get a port number for a data connection.
394412 func (c *ServerConn) pasv() (host string, port int, err error) {
395413 _, line, err := c.cmd(StatusPassiveMode, "PASV")
396414 if err != nil {
397 return
415 return "", 0, err
398416 }
399417
400418 // PASV response format : 227 Entering Passive Mode (h1,h2,h3,h4,p1,p2).
401419 start := strings.Index(line, "(")
402420 end := strings.LastIndex(line, ")")
403421 if start == -1 || end == -1 {
404 err = errors.New("invalid PASV response format")
405 return
422 return "", 0, errors.New("invalid PASV response format")
406423 }
407424
408425 // We have to split the response string
409426 pasvData := strings.Split(line[start+1:end], ",")
410427
411428 if len(pasvData) < 6 {
412 err = errors.New("invalid PASV response format")
413 return
429 return "", 0, errors.New("invalid PASV response format")
414430 }
415431
416432 // Let's compute the port number
417 portPart1, err1 := strconv.Atoi(pasvData[4])
418 if err1 != nil {
419 err = err1
420 return
421 }
422
423 portPart2, err2 := strconv.Atoi(pasvData[5])
424 if err2 != nil {
425 err = err2
426 return
433 portPart1, err := strconv.Atoi(pasvData[4])
434 if err != nil {
435 return "", 0, err
436 }
437
438 portPart2, err := strconv.Atoi(pasvData[5])
439 if err != nil {
440 return "", 0, err
427441 }
428442
429443 // Recompose port
431445
432446 // Make the IP address to connect to
433447 host = strings.Join(pasvData[0:4], ".")
434 return
448 return host, port, nil
435449 }
436450
437451 // getDataConnPort returns a host, port for a new data connection
486500 // cmdDataConnFrom executes a command which require a FTP data connection.
487501 // Issues a REST FTP command to specify the number of bytes to skip for the transfer.
488502 func (c *ServerConn) cmdDataConnFrom(offset uint64, format string, args ...interface{}) (net.Conn, error) {
503 // If server requires PRET send the PRET command to warm it up
504 // See: https://tools.ietf.org/html/draft-dd-pret-00
505 if c.usePRET {
506 _, _, err := c.cmd(-1, "PRET "+format, args...)
507 if err != nil {
508 return nil, err
509 }
510 }
511
489512 conn, err := c.openDataConn()
490513 if err != nil {
491514 return nil, err
492515 }
493516
494517 if offset != 0 {
495 _, _, err := c.cmd(StatusRequestFilePending, "REST %d", offset)
518 _, _, err = c.cmd(StatusRequestFilePending, "REST %d", offset)
496519 if err != nil {
497 conn.Close()
520 _ = conn.Close()
498521 return nil, err
499522 }
500523 }
501524
502525 _, err = c.conn.Cmd(format, args...)
503526 if err != nil {
504 conn.Close()
527 _ = conn.Close()
505528 return nil, err
506529 }
507530
508531 code, msg, err := c.conn.ReadResponse(-1)
509532 if err != nil {
510 conn.Close()
533 _ = conn.Close()
511534 return nil, err
512535 }
513536 if code != StatusAlreadyOpen && code != StatusAboutToSend {
514 conn.Close()
537 _ = conn.Close()
515538 return nil, &textproto.Error{Code: code, Msg: msg}
516539 }
517540
520543
521544 // NameList issues an NLST FTP command.
522545 func (c *ServerConn) NameList(path string) (entries []string, err error) {
523 conn, err := c.cmdDataConnFrom(0, "NLST %s", path)
524 if err != nil {
525 return
546 space := " "
547 if path == "" {
548 space = ""
549 }
550 conn, err := c.cmdDataConnFrom(0, "NLST%s%s", space, path)
551 if err != nil {
552 return nil, err
526553 }
527554
528555 r := &Response{conn: conn, c: c}
529 defer r.Close()
556 defer func() {
557 errClose := r.Close()
558 if err == nil {
559 err = errClose
560 }
561 }()
530562
531563 scanner := bufio.NewScanner(r)
532564 for scanner.Scan() {
533565 entries = append(entries, scanner.Text())
534566 }
535 if err = scanner.Err(); err != nil {
536 return entries, err
537 }
538 return
567
568 err = scanner.Err()
569 return entries, err
539570 }
540571
541572 // List issues a LIST FTP command.
551582 parser = parseListLine
552583 }
553584
554 conn, err := c.cmdDataConnFrom(0, "%s %s", cmd, path)
555 if err != nil {
556 return
585 space := " "
586 if path == "" {
587 space = ""
588 }
589 conn, err := c.cmdDataConnFrom(0, "%s%s%s", cmd, space, path)
590 if err != nil {
591 return nil, err
557592 }
558593
559594 r := &Response{conn: conn, c: c}
560 defer r.Close()
595 defer func() {
596 errClose := r.Close()
597 if err == nil {
598 err = errClose
599 }
600 }()
561601
562602 scanner := bufio.NewScanner(r)
563603 now := time.Now()
564604 for scanner.Scan() {
565 entry, err := parser(scanner.Text(), now, c.options.location)
566 if err == nil {
605 entry, errParse := parser(scanner.Text(), now, c.options.location)
606 if errParse == nil {
567607 entries = append(entries, entry)
568608 }
569609 }
570 if err := scanner.Err(); err != nil {
571 return nil, err
572 }
573 return
610
611 err = scanner.Err()
612 return entries, err
574613 }
575614
576615 // ChangeDir issues a CWD FTP command, which changes the current directory to
662701 // the response and we cannot use the connection to send other commands.
663702 // So we don't check io.Copy error and we return the error from
664703 // ReadResponse so the user can see the real error
665 io.Copy(conn, r)
666 conn.Close()
667
668 _, _, err = c.conn.ReadResponse(StatusClosingDataConnection)
704 var n int64
705 n, err = io.Copy(conn, r)
706
707 // If we wrote no bytes but got no error, make sure we call
708 // tls.Handshake on the connection as it won't get called
709 // unless Write() is called.
710 //
711 // ProFTP doesn't like this and returns "Unable to build data
712 // connection: Operation not permitted" when trying to upload
713 // an empty file without this.
714 if n == 0 && err == nil {
715 if do, ok := conn.(interface{ Handshake() error }); ok {
716 err = do.Handshake()
717 }
718 }
719
720 // Use io.Copy or Handshake error in preference to this one
721 closeErr := conn.Close()
722 if err == nil {
723 err = closeErr
724 }
725
726 // Read the response and use this error in preference to
727 // previous errors
728 _, _, respErr := c.conn.ReadResponse(StatusClosingDataConnection)
729 if respErr != nil {
730 err = respErr
731 }
669732 return err
670733 }
671734
681744 }
682745
683746 // see the comment for StorFrom above
684 io.Copy(conn, r)
685 conn.Close()
686
687 _, _, err = c.conn.ReadResponse(StatusClosingDataConnection)
747 _, err = io.Copy(conn, r)
748 errClose := conn.Close()
749
750 _, _, respErr := c.conn.ReadResponse(StatusClosingDataConnection)
751 if respErr != nil {
752 err = respErr
753 }
754
755 if err == nil {
756 err = errClose
757 }
758
688759 return err
689760 }
690761
792863 // Quit issues a QUIT FTP command to properly close the connection from the
793864 // remote FTP server.
794865 func (c *ServerConn) Quit() error {
795 c.conn.Cmd("QUIT")
796 return c.conn.Close()
866 _, errQuit := c.conn.Cmd("QUIT")
867 err := c.conn.Close()
868
869 if errQuit != nil {
870 if err != nil {
871 return fmt.Errorf("error while quitting: %s: %w", errQuit, err)
872 }
873 return errQuit
874 }
875
876 return err
797877 }
798878
799879 // Read implements the io.Reader interface on a FTP data connection.
6262 e.Type = EntryTypeFile
6363 }
6464 case "size":
65 e.setSize(value)
65 if err := e.setSize(value); err != nil {
66 return nil, err
67 }
6668 }
6769 }
6870 return e, nil
159159 for _, test := range tests {
160160 t.Run(test.line, func(t *testing.T) {
161161 entry := &Entry{}
162 entry.setTime(strings.Fields(test.line), now, time.UTC)
162 if err := entry.setTime(strings.Fields(test.line), now, time.UTC); err != nil {
163 t.Fatal(err)
164 }
163165
164166 assert.Equal(t, test.expected, entry.Time)
165167 })
00 package ftp
11
2 import "testing"
3 import "github.com/stretchr/testify/assert"
2 import (
3 "testing"
4
5 "github.com/stretchr/testify/assert"
6 )
47
58 func TestScanner(t *testing.T) {
69 assert := assert.New(t)
3535 if w.descend && w.cur.entry.Type == EntryTypeFolder {
3636 entries, err := w.serverConn.List(w.cur.path)
3737
38 // an error occured, drop out and stop walking
38 // an error occurred, drop out and stop walking
3939 if err != nil {
4040 w.cur.err = err
4141 return false
103103 assert, require := assert.New(t), require.New(t)
104104
105105 mock, err := newFtpMock(t, "127.0.0.1")
106 require.NotNil(err)
107 defer mock.Close()
108
109 c, cErr := Connect(mock.Addr())
110 require.NotNil(cErr)
106 require.Nil(err)
107 defer mock.Close()
108
109 c, cErr := Connect(mock.Addr())
110 require.Nil(cErr)
111111
112112 w := c.Walk("/root")
113113
135135 assert, require := assert.New(t), require.New(t)
136136
137137 mock, err := newFtpMock(t, "127.0.0.1")
138 require.NotNil(err)
139 defer mock.Close()
140
141 c, cErr := Connect(mock.Addr())
142 require.NotNil(cErr)
138 require.Nil(err)
139 defer mock.Close()
140
141 c, cErr := Connect(mock.Addr())
142 require.Nil(cErr)
143143
144144 w := c.Walk("/root")
145145 w.cur = &item{
177177 }
178178
179179 result := w.Next()
180 assert.Equal(true, result, "Result should return true")
181
180182 result = w.Next()
181183
182184 assert.Equal(true, result, "Result should return true")