2015-12-28 14:51:24 +00:00
package sftp
import (
"net/url"
2016-02-15 15:58:13 +00:00
"path"
2015-12-28 14:51:24 +00:00
"strings"
2016-08-21 15:46:23 +00:00
2017-07-23 12:21:03 +00:00
"github.com/restic/restic/internal/errors"
"github.com/restic/restic/internal/options"
2015-12-28 14:51:24 +00:00
)
// Config collects all information required to connect to an sftp server.
type Config struct {
2020-02-19 14:33:52 +00:00
User , Host , Port , Path string
Layout string ` option:"layout" help:"use this backend directory layout (default: auto-detect)" `
Command string ` option:"command" help:"specify command to create sftp connection" `
2021-08-07 17:56:59 +00:00
Connections uint ` option:"connections" help:"set a limit for the number of concurrent connections (default: 5)" `
}
// NewConfig returns a new config with default options applied.
func NewConfig ( ) Config {
return Config {
Connections : 5 ,
}
2017-04-13 21:55:32 +00:00
}
func init ( ) {
options . Register ( "sftp" , Config { } )
2015-12-28 14:51:24 +00:00
}
2016-02-15 15:58:13 +00:00
// ParseConfig parses the string s and extracts the sftp config. The
2020-02-19 14:33:52 +00:00
// supported configuration formats are sftp://user@host[:port]/directory
2022-08-19 17:12:26 +00:00
// and sftp:user@host:directory. The directory will be path Cleaned and can
// be an absolute path if it starts with a '/' (e.g.
// sftp://user@host//absolute and sftp:user@host:/absolute).
2023-04-20 20:40:21 +00:00
func ParseConfig ( s string ) ( Config , error ) {
2020-02-19 14:33:52 +00:00
var user , host , port , dir string
2016-02-15 15:58:13 +00:00
switch {
case strings . HasPrefix ( s , "sftp://" ) :
// parse the "sftp://user@host/path" url format
url , err := url . Parse ( s )
if err != nil {
2023-04-20 20:40:21 +00:00
return Config { } , errors . WithStack ( err )
2016-02-15 15:58:13 +00:00
}
if url . User != nil {
user = url . User . Username ( )
}
2020-02-19 14:33:52 +00:00
host = url . Hostname ( )
port = url . Port ( )
2016-08-28 10:02:07 +00:00
dir = url . Path
if dir == "" {
2023-04-20 20:40:21 +00:00
return Config { } , errors . Errorf ( "invalid backend %q, no directory specified" , s )
2016-08-28 10:02:07 +00:00
}
dir = dir [ 1 : ]
2016-02-15 15:58:13 +00:00
case strings . HasPrefix ( s , "sftp:" ) :
// parse the sftp:user@host:path format, which means we'll get
// "user@host:path" in s
s = s [ 5 : ]
// split user@host and path at the colon
2022-11-27 17:09:59 +00:00
var colon bool
host , dir , colon = strings . Cut ( s , ":" )
if ! colon {
2023-04-20 20:40:21 +00:00
return Config { } , errors . New ( "sftp: invalid format, hostname or path not found" )
2016-02-15 15:58:13 +00:00
}
// split user and host at the "@"
2022-11-27 17:09:59 +00:00
data := strings . SplitN ( host , "@" , 3 )
2019-09-26 10:23:31 +00:00
if len ( data ) == 3 {
user = data [ 0 ] + "@" + data [ 1 ]
host = data [ 2 ]
} else if len ( data ) == 2 {
2016-02-15 15:58:13 +00:00
user = data [ 0 ]
host = data [ 1 ]
}
default :
2023-04-20 20:40:21 +00:00
return Config { } , errors . New ( ` invalid format, does not start with "sftp:" ` )
2015-12-28 14:51:24 +00:00
}
2017-09-30 08:34:23 +00:00
p := path . Clean ( dir )
if strings . HasPrefix ( p , "~" ) {
2023-04-20 20:40:21 +00:00
return Config { } , errors . New ( "sftp path starts with the tilde (~) character, that fails for most sftp servers.\nUse a relative directory, most servers interpret this as relative to the user's home directory" )
2017-09-30 08:34:23 +00:00
}
2021-08-07 17:56:59 +00:00
cfg := NewConfig ( )
cfg . User = user
cfg . Host = host
cfg . Port = port
cfg . Path = p
return cfg , nil
2015-12-28 14:51:24 +00:00
}