sub _fetch_uri { my ($self, $uri)= @_; my $conn= $self->_cached_connection($uri); if ($uri->scheme eq 'ftp') { my $buf= ''; open my $fh, '>', \$buf or die; $conn->get($uri->path, $fh) or die 'file could not be downloaded: ' . $conn->message; close $fh; return $buf; } elsif ($uri->scheme eq 'sftp') { return scalar $conn->get($uri->path); } elsif ($uri->scheme eq 'file') { return $conn->slurp_raw; } else { die "BUG: Unhandled scheme ".$uri->scheme; } } # Return arrayref of file names under the URI path # sub _readdir_uri { my ($self, $uri)= @_; my $conn= $self->_cached_connection($uri); if ($uri->scheme eq 'ftp') { # connection refers to root filesystem. path can be multiple components # deep reaching into the file tree. Then, need to remove all directory portions # of the response. return [ map { $_ =~ s|.*/||; $_ } $conn->ls($uri->path) ]; } elsif ($uri->scheme eq 'sftp') { return [ map $_->{filename}, $conn->ls($uri->path) ]; } elsif ($uri->scheme eq 'file') { return [ $conn->children ]; } else { die "BUG: Unhandled scheme ".$uri->scheme; } } sub _cached_connection { my ($self, $uri)= @_; if ($uri->scheme eq 'ftp') { return ($self->_conn_cache->{'ftp:'.$uri->user.'@'.$uri->host} //= $self->_connect_ftp($uri)); } elsif ($uri->scheme eq 'sftp') { return ($self->_conn_cache->{'sftp:'.$uri->user.'@'.$uri->host} //= $self->_connect_sftp($uri)); } elsif ($uri->scheme eq 'file') { _logcroak('Refusing to access local file URL') unless $ENV{DATA_SERVICE_ACCESS_LOCAL}; # for debugging return Path::Tiny::path($uri->file); } else { _logcroak("Don't know how to access ".$self->_redacted_uri($uri)); } } sub _connect_ftp { my ($self, $uri)= @_; my $host= $uri->host; my $ftp = Net::FTP->new($host, Passive => 1) or die "Cannot connect to '$host': $@"; $ftp->login($uri->user, $uri->password) or die 'Cannot login: ', $ftp->message; $ftp->binary or die "Can't start binary mode"; return $ftp } sub _connect_sftp { my ($self, $uri)= @_; my $host= $uri->host; Net::SFTP->new( $host, user => $uri->user, password => $uri->password, ssh_args => [ # See https://stackoverflow.com/questions/41964908/netsftp-perl-rsa-authentification/51863332#51863332 identity_files => [], # deprecated, but needed for ancient servers options => [ "MACs +hmac-sha1" ], ], ); }