99 "io"
1010 "net"
1111 "net/textproto"
12+ "net/url"
1213 "path"
1314 "runtime"
1415 "strings"
@@ -185,6 +186,14 @@ Supports the format user:pass@host:port, user@host:port, host:port.
185186Example:
186187
187188 myUser:myPass@localhost:9005
189+ ` ,
190+ Advanced : true ,
191+ }, {
192+ Name : "http_proxy" ,
193+ Default : "" ,
194+ Help : `URL for HTTP CONNECT proxy
195+
196+ Set this to a URL for an HTTP proxy which supports the HTTP CONNECT verb.
188197` ,
189198 Advanced : true ,
190199 }, {
@@ -248,6 +257,7 @@ type Options struct {
248257 AskPassword bool `config:"ask_password"`
249258 Enc encoder.MultiEncoder `config:"encoding"`
250259 SocksProxy string `config:"socks_proxy"`
260+ HTTPProxy string `config:"http_proxy"`
251261 NoCheckUpload bool `config:"no_check_upload"`
252262}
253263
@@ -266,6 +276,7 @@ type Fs struct {
266276 pool []* ftp.ServerConn
267277 drain * time.Timer // used to drain the pool when we stop using the connections
268278 tokens * pacer.TokenDispenser
279+ proxyURL * url.URL // address of HTTP proxy read from environment
269280 pacer * fs.Pacer // pacer for FTP connections
270281 fGetTime bool // true if the ftp library accepts GetTime
271282 fSetTime bool // true if the ftp library accepts SetTime
@@ -413,11 +424,26 @@ func (f *Fs) ftpConnection(ctx context.Context) (c *ftp.ServerConn, err error) {
413424 dial := func (network , address string ) (conn net.Conn , err error ) {
414425 fs .Debugf (f , "dial(%q,%q)" , network , address )
415426 defer func () {
416- fs .Debugf (f , "> dial: conn=%T, err=%v" , conn , err )
427+ if err != nil {
428+ fs .Debugf (f , "> dial: conn=%v, err=%v" , conn , err )
429+ } else {
430+ fs .Debugf (f , "> dial: conn=%s->%s, err=%v" , conn .LocalAddr (), conn .RemoteAddr (), err )
431+ }
417432 }()
418433 baseDialer := fshttp .NewDialer (ctx )
419434 if f .opt .SocksProxy != "" {
420435 conn , err = proxy .SOCKS5Dial (network , address , f .opt .SocksProxy , baseDialer )
436+ } else if f .proxyURL != nil {
437+ // We need to make the onward connection to f.opt.Host. However the FTP
438+ // library sets the host to the proxy IP after using EPSV or PASV so we need
439+ // to correct that here.
440+ var dialPort string
441+ _ , dialPort , err = net .SplitHostPort (address )
442+ if err != nil {
443+ return nil , err
444+ }
445+ dialAddress := net .JoinHostPort (f .opt .Host , dialPort )
446+ conn , err = proxy .HTTPConnectDial (network , dialAddress , f .proxyURL , baseDialer )
421447 } else {
422448 conn , err = baseDialer .Dial (network , address )
423449 }
@@ -631,6 +657,14 @@ func NewFs(ctx context.Context, name, root string, m configmap.Mapper) (ff fs.Fs
631657 CanHaveEmptyDirectories : true ,
632658 PartialUploads : true ,
633659 }).Fill (ctx , f )
660+ // get proxy URL if set
661+ if opt .HTTPProxy != "" {
662+ proxyURL , err := url .Parse (opt .HTTPProxy )
663+ if err != nil {
664+ return nil , fmt .Errorf ("failed to parse HTTP Proxy URL: %w" , err )
665+ }
666+ f .proxyURL = proxyURL
667+ }
634668 // set the pool drainer timer going
635669 if f .opt .IdleTimeout > 0 {
636670 f .drain = time .AfterFunc (time .Duration (opt .IdleTimeout ), func () { _ = f .drainPool (ctx ) })
0 commit comments