Skip to content

Commit

Permalink
added FileSystem.CleanPathFromURI, FTP and SFTP default to URLs witho…
Browse files Browse the repository at this point in the history
…ut port
  • Loading branch information
ungerik committed Apr 5, 2024
1 parent 4cc9499 commit 4f2a457
Show file tree
Hide file tree
Showing 16 changed files with 148 additions and 32 deletions.
4 changes: 4 additions & 0 deletions dropboxfs/dropboxfs.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,10 @@ func (dbfs *fileSystem) URL(cleanPath string) string {
return dbfs.prefix + cleanPath
}

func (dbfs *fileSystem) CleanPathFromURI(uri string) string {
return strings.TrimPrefix(uri, dbfs.prefix)
}

func (dbfs *fileSystem) JoinCleanPath(uriParts ...string) string {
return fsimpl.JoinCleanPath(uriParts, dbfs.prefix, Separator)
}
Expand Down
9 changes: 8 additions & 1 deletion filesystem.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,16 @@ type FileSystem interface {
// String returns a descriptive string for the FileSystem implementation
String() string

// URL returns a full URL wich is Prefix() + cleanPath
// URL returns a full URL wich is Prefix() + cleanPath.
// Note that the passed cleanPath will not be cleaned
// by the FileSystem implementation.
URL(cleanPath string) string

// CleanPathFromURI returns the clean path part of an URI
// specific to the implementation of the FileSystem.
// It's the inverse of the URL method.
CleanPathFromURI(uri string) string

// JoinCleanFile joins the file system prefix with uriParts
// into a File with clean path and prefix
JoinCleanFile(uriParts ...string) File
Expand Down
13 changes: 12 additions & 1 deletion fsimpl/fsimpl.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,18 @@ func JoinCleanPath(uriParts []string, trimPrefix, separator string) string {
if !strings.HasPrefix(cleanPath, separator) {
cleanPath = separator + cleanPath
}
return path.Clean(cleanPath)
return path.Clean(cleanPath) // TODO works only when separator is "/"
}

func CleanPath(p, separator string) string {
unescPath, err := url.PathUnescape(p)
if err == nil {
p = unescPath
}
if !strings.HasPrefix(p, separator) {
p = separator + p
}
return path.Clean(p) // TODO works only when separator is "/"
}

func SplitPath(filePath, prefix, separator string) []string {
Expand Down
37 changes: 29 additions & 8 deletions ftpfs/ftp.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"io"
iofs "io/fs"
"net/url"
"path"
"strings"
"time"

Expand Down Expand Up @@ -87,16 +88,22 @@ func Dial(ctx context.Context, address string, credentialsCallback CredentialsCa
}, nil
}

func dial(ctx context.Context, address, username, password string, secure bool) (conn *ftp.ServerConn, err error) {
func dial(ctx context.Context, host, username, password string, secure bool) (conn *ftp.ServerConn, err error) {
if secure {
if !strings.ContainsRune(host, ':') {
host += ":990"
}
conn, err = ftp.Dial(
address,
host,
ftp.DialWithContext(ctx),
ftp.DialWithTLS(&tls.Config{InsecureSkipVerify: true}), // DialWithExplicitTLS also possible
)
} else {
if !strings.ContainsRune(host, ':') {
host += ":21"
}
conn, err = ftp.Dial(
address,
host,
ftp.DialWithContext(ctx),
)
}
Expand Down Expand Up @@ -168,8 +175,12 @@ func prepareDial(address string, credentialsCallback CredentialsCallback) (u *ur
if u.Scheme != "ftp" && u.Scheme != "ftps" {
return nil, "", "", "", false, fmt.Errorf("not an FTP or FTPS URL scheme: %s", address)
}
if u.Port() == "" {
u.Host += ":21"
// Trim default port number
switch u.Scheme {
case "ftp":
u.Host = strings.TrimSuffix(u.Host, ":21")
case "ftps":
u.Host = strings.TrimSuffix(u.Host, ":990")
}

username, password, err = credentialsCallback(u)
Expand Down Expand Up @@ -241,10 +252,20 @@ func (f *fileSystem) String() string {
}

func (f *fileSystem) URL(cleanPath string) string {
return f.prefix + cleanPath
}

func (f *fileSystem) CleanPathFromURI(uri string) string {
port := ":21"
if f.secure {
return PrefixTLS + cleanPath
}
return Prefix + cleanPath
port = ":990"
}
return path.Clean(
strings.TrimPrefix(
strings.TrimPrefix(uri, f.prefix),
port, // In case f.prefix has no port number and url has the default port number
),
)
}

func (f *fileSystem) JoinCleanPath(uriParts ...string) string {
Expand Down
42 changes: 33 additions & 9 deletions ftpfs/ftp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,29 +16,53 @@ func checkAndReadFile(t *testing.T, f fs.File) []byte {
assert.True(t, f.Exists(), "Exists")
assert.False(t, f.IsDir(), "not IsDir")
data, err := f.ReadAll()
assert.NoError(t, err)
require.NoError(t, err)
return data
}

func TestDialAndRegister(t *testing.T) {
// TODO FTPS
{
ftpFS, err := DialAndRegister(context.Background(), "ftp://demo@test.rebex.net:21", Password("password"))
ftpFS, err := DialAndRegister(context.Background(), "ftp://demo@test.rebex.net", Password("password"))
require.NoError(t, err, "Dial")

require.Equal(t, "ftp://demo@test.rebex.net:21", ftpFS.Prefix())
require.Equal(t, "ftp://demo@test.rebex.net", ftpFS.Prefix())
id, err := ftpFS.ID()
require.NoError(t, err)
require.Equal(t, "ftp://demo@test.rebex.net:21", id)
require.Equal(t, "ftp://demo@test.rebex.net:21 file system", ftpFS.String())
require.Equal(t, "ftp://demo@test.rebex.net", id)
require.Equal(t, "ftp://demo@test.rebex.net file system", ftpFS.String())
require.Equal(t, "FTP", ftpFS.Name())
require.Equal(t, "/a/b", ftpFS.JoinCleanPath("a", "skip", "..", "/", "b", "/"))
require.Equal(t, fs.File("ftp://demo@test.rebex.net:21/a/b"), ftpFS.JoinCleanFile("a", "skip", "..", "/", "b", "/"))
require.Equal(t, fs.File("ftp://demo@test.rebex.net/a/b"), ftpFS.JoinCleanFile("a", "skip", "..", "/", "b", "/"))

f := fs.File("ftp://demo@test.rebex.net:21/readme.txt")
f := fs.File("ftp://demo@test.rebex.net/readme.txt")
assert.Equal(t, "readme.txt", f.Name())
data := checkAndReadFile(t, f)
assert.True(t, len(data) > 0)
assert.True(t, len(data) > 0, "read more than zero bytes from readme.txt")

// files, err := fs.File("ftp://test.rebex.net:21/").ListDirMax(-1)
// fmt.Println(files)
// t.Fatal("todo")

err = ftpFS.Close()
require.NoError(t, err, "Close")
}
{
ftpFS, err := DialAndRegister(context.Background(), "ftps://demo@test.rebex.net", Password("password"))
require.NoError(t, err, "Dial")

require.Equal(t, "ftps://demo@test.rebex.net", ftpFS.Prefix())
id, err := ftpFS.ID()
require.NoError(t, err)
require.Equal(t, "ftps://demo@test.rebex.net", id)
require.Equal(t, "ftps://demo@test.rebex.net file system", ftpFS.String())
require.Equal(t, "FTPS", ftpFS.Name())
require.Equal(t, "/a/b", ftpFS.JoinCleanPath("a", "skip", "..", "/", "b", "/"))
require.Equal(t, fs.File("ftps://demo@test.rebex.net/a/b"), ftpFS.JoinCleanFile("a", "skip", "..", "/", "b", "/"))

f := fs.File("ftps://demo@test.rebex.net/readme.txt")
assert.Equal(t, "readme.txt", f.Name())
// data := checkAndReadFile(t, f)
// assert.True(t, len(data) > 0, "read more than zero bytes from readme.txt") // TODO: fails for some reason

// files, err := fs.File("ftp://test.rebex.net:21/").ListDirMax(-1)
// fmt.Println(files)
Expand Down
4 changes: 4 additions & 0 deletions httpfs/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ func (f *fileSystem) URL(cleanPath string) string {
return f.prefix + cleanPath
}

func (f *fileSystem) CleanPathFromURI(uri string) string {
return path.Clean(strings.TrimPrefix(uri, f.prefix))
}

func (f *fileSystem) JoinCleanFile(uriParts ...string) fs.File {
return fs.File(f.prefix + f.JoinCleanPath(uriParts...))
}
Expand Down
4 changes: 4 additions & 0 deletions invalidfilesystem.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,10 @@ func (fs InvalidFileSystem) URL(cleanPath string) string {
return fs.Prefix() + cleanPath
}

func (fs InvalidFileSystem) CleanPathFromURI(uri string) string {
return path.Clean(strings.TrimPrefix(uri, fs.Prefix()))
}

func (fs InvalidFileSystem) JoinCleanPath(uriParts ...string) string {
if len(uriParts) > 0 {
uriParts[0] = strings.TrimPrefix(uriParts[0], fs.Prefix())
Expand Down
10 changes: 10 additions & 0 deletions localfilesystem.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,16 @@ func (local *LocalFileSystem) URL(cleanPath string) string {
return LocalPrefix + filepath.ToSlash(local.AbsPath(cleanPath))
}

func (local *LocalFileSystem) CleanPathFromURI(uri string) string {
cleanPath := strings.TrimPrefix(uri, LocalPrefix)
if cleanPath != "" && !strings.HasPrefix(cleanPath, Separator) {
cleanPath = Separator + cleanPath
}
cleanPath = filepath.Clean(cleanPath)
cleanPath = expandTilde(cleanPath)
return cleanPath
}

func (local *LocalFileSystem) JoinCleanPath(uriParts ...string) string {
if len(uriParts) > 0 {
uriParts[0] = strings.TrimPrefix(uriParts[0], LocalPrefix)
Expand Down
4 changes: 4 additions & 0 deletions memfilesystem.go
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,10 @@ func (fs *MemFileSystem) URL(cleanPath string) string {
return fs.prefix + cleanPath
}

func (fs *MemFileSystem) CleanPathFromURI(uri string) string {
return strings.TrimPrefix(uri, fs.prefix)
}

func (fs *MemFileSystem) JoinCleanPath(uriParts ...string) string {
if len(uriParts) > 0 {
uriParts[0] = strings.TrimPrefix(uriParts[0], fs.prefix)
Expand Down
4 changes: 4 additions & 0 deletions multipartfs/multipartfs.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,10 @@ func (f *MultipartFileSystem) URL(cleanPath string) string {
return f.prefix + cleanPath
}

func (f *MultipartFileSystem) CleanPathFromURI(uri string) string {
return strings.TrimPrefix(uri, f.prefix)
}

func (f *MultipartFileSystem) JoinCleanPath(uriParts ...string) string {
return fsimpl.JoinCleanPath(uriParts, f.prefix, Separator)
}
Expand Down
9 changes: 4 additions & 5 deletions registry.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package fs

import (
"cmp"
"fmt"
"slices"
"sort"
"strings"
"sync"
)
Expand Down Expand Up @@ -55,7 +55,7 @@ func Register(fs FileSystem) int {

registry[prefix] = &fsCount{fs, 1}
registrySorted = append(registrySorted, fs)
sort.Slice(registrySorted, func(i, j int) bool { return registrySorted[i].Prefix() < registrySorted[j].Prefix() })
slices.SortFunc(registrySorted, func(a, b FileSystem) int { return cmp.Compare(a.Prefix(), b.Prefix()) })
return 1
}

Expand Down Expand Up @@ -145,9 +145,8 @@ func ParseRawURI(uri string) (fs FileSystem, fsPath string) {
// by iterating in reverse order of sorted registry
for i := len(registrySorted) - 1; i >= 0; i-- {
fs = registrySorted[i]
path, found := strings.CutPrefix(uri, fs.Prefix())
if found {
return fs, path
if strings.HasPrefix(uri, fs.Prefix()) {
return fs, fs.CleanPathFromURI(uri)
}
}

Expand Down
4 changes: 4 additions & 0 deletions s3fs/s3fs.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,10 @@ func (s *fileSystem) URL(cleanPath string) string {
return s.prefix + cleanPath
}

func (f *fileSystem) CleanPathFromURI(uri string) string {
return strings.TrimPrefix(uri, f.prefix)
}

func (s *fileSystem) JoinCleanFile(uriParts ...string) fs.File {
return fs.File(s.prefix + s.JoinCleanPath(uriParts...))
}
Expand Down
21 changes: 16 additions & 5 deletions sftpfs/sftp.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"net"
"net/url"
"os"
"path"
"strings"

"github.com/pkg/sftp"
Expand Down Expand Up @@ -110,9 +111,8 @@ func prepareDial(address string, credentialsCallback CredentialsCallback, hostKe
if u.Scheme != "sftp" {
return nil, "", "", "", fmt.Errorf("not an SFTP URL scheme: %s", address)
}
if u.Port() == "" {
u.Host += ":22"
}
// Trim default port number
u.Host = strings.TrimSuffix(u.Host, ":22")

username, password, err = credentialsCallback(u)
if err != nil {
Expand Down Expand Up @@ -180,6 +180,9 @@ func dial(ctx context.Context, host, user, password string, hostKeyCallback ssh.
HostKeyCallback: hostKeyCallback,
}
d := net.Dialer{}
if !strings.ContainsRune(host, ':') {
host += ":22"
}
conn, err := d.DialContext(ctx, "tcp", host)
if err != nil {
return nil, err
Expand Down Expand Up @@ -263,8 +266,16 @@ func (f *fileSystem) String() string {
}

func (f *fileSystem) URL(cleanPath string) string {
// Prefix was trimmed from cleanPath in JoinCleanPath
return Prefix + cleanPath
return f.prefix + cleanPath
}

func (f *fileSystem) CleanPathFromURI(uri string) string {
return path.Clean(
strings.TrimPrefix(
strings.TrimPrefix(uri, f.prefix),
":22", // In case f.prefix has no port number and url has the default port number
),
)
}

func (*fileSystem) JoinCleanPath(uriParts ...string) string {
Expand Down
7 changes: 6 additions & 1 deletion subfilesystem.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
iofs "io/fs"
"path/filepath"
"strings"

"github.com/ungerik/go-fs/fsimpl"
)
Expand Down Expand Up @@ -73,7 +74,11 @@ func (subfs *todoSubFileSystem) URN(filePath string) string {
}

func (subfs *todoSubFileSystem) URL(filePath string) string {
return LocalPrefix + subfs.URN(filePath)
return subfs.prefix + subfs.URN(filePath)
}

func (subfs *todoSubFileSystem) CleanPathFromURI(uri string) string {
return strings.TrimPrefix(uri, subfs.prefix)
}

func (subfs *todoSubFileSystem) JoinCleanPath(uri ...string) string {
Expand Down
4 changes: 2 additions & 2 deletions utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,12 +83,12 @@ func ReadAllContext(ctx context.Context, r io.Reader) ([]byte, error) {
b = b[:len(b)+n]
if err != nil {
if err == io.EOF {
err = nil
return b, nil
}
return b, err
}
}
return nil, ctx.Err()
return b, ctx.Err()
}

// WriteAllContext writes all data wo the to w
Expand Down
4 changes: 4 additions & 0 deletions zipfs/zipfs.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,10 @@ func (f *ZipFileSystem) URL(cleanPath string) string {
return f.prefix + cleanPath
}

func (f *ZipFileSystem) CleanPathFromURI(uri string) string {
return path.Clean(strings.TrimPrefix(uri, f.prefix))
}

func (f *ZipFileSystem) JoinCleanPath(uriParts ...string) string {
return fsimpl.JoinCleanPath(uriParts, f.prefix, Separator)
}
Expand Down

0 comments on commit 4f2a457

Please sign in to comment.