aferodav

package module
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 7, 2026 License: MIT Imports: 10 Imported by: 0

README

aferodav

github.com/lib-x/aferodav — bidirectional adapters between golang.org/x/net/webdav and github.com/spf13/afero.

Two adapters

New — webdav.FileSystem → afero.Fs

Wrap any webdav.FileSystem so it can be used wherever afero.Fs is expected.

var wdfs webdav.FileSystem = myWebDAVBackend()
afs := aferodav.New(wdfs, context.Background())

// use with any afero-aware library
data, _ := afero.ReadFile(afs, "/notes/hello.txt")
NewFS — afero.Fs → webdav.FileSystem

Wrap any afero.Fs to serve it over WebDAV.

afs := afero.NewMemMapFs() // or OsFs, BasePathFs, your R2Fs, …
handler := &webdav.Handler{
    FileSystem: aferodav.NewFS(afs),
    LockSystem: webdav.NewMemLS(),
}
http.ListenAndServe(":8080", handler)

Installation

go get github.com/lib-x/aferodav

Supported afero backends

Any afero.Fs works with NewFS:

Backend Notes
afero.NewMemMapFs() In-memory, great for tests
afero.NewOsFs() Real OS filesystem
afero.NewBasePathFs(base, dir) Chroot-like sandbox
afero.NewReadOnlyFs(base) Read-only view
afero.NewCopyOnWriteFs(base, overlay) Overlay writes on a read-only base
Custom (S3, GCS, R2, SFTP …) Anything implementing afero.Fs

Behaviour notes

Feature New (webdav→afero) NewFS (afero→webdav)
MkdirAll Emulated via repeated Mkdir Delegates to afero.Fs.MkdirAll
Auto-create parent dirs Disabled by default; enable with WithAutoMkdirParents()
Recursive WebDAV MKCOL Disabled by default; enable with WithRecursiveMkdir()
Path validation before path.Clean Uses built-in normalization Disabled by default; enable with WithPathCleaner(...)
OpenFile flag rewriting Disabled by default; enable with WithOpenFileFlagMapper(...) or WithObjectStoreWriteMode()
Synthetic Stat for streaming writes Disabled by default; enable with WithSyntheticWriteStat()
Stat fallback / implicit directories Disabled by default; enable with WithStatFallback(...) or WithImplicitDirectoryStat(...)
ReadAt / WriteAt Emulated via locked Seek + Read/Write Delegates to afero
Truncate Emulated (shrink: copy+rewrite) Delegates to afero
Sync No-op Delegates to afero
Chmod / Chown / Chtimes Unsupported (not in WebDAV)
// Optional convenience mode: create missing parents on WebDAV create/rename.
handler := &webdav.Handler{
    FileSystem: aferodav.NewFS(afs, aferodav.WithAutoMkdirParents()),
    LockSystem: webdav.NewMemLS(),
}

NewFS options

NewFS keeps normal webdav.FileSystem / os semantics by default. The options below are opt-in because object stores and regular filesystems need different behavior.

Option Default result Enabled result
WithAutoMkdirParents() OpenFile(O_CREATE) and Rename fail when the destination parent is missing. Missing parent directories are created before create or rename.
WithPathCleaner(cleaner) Paths are normalized with path.Clean; for example /a/../b becomes /b. cleaner(op, rawName) runs before normalization and can reject or rewrite the raw path. Use this to preserve stricter path safety rules such as rejecting .., \, Windows drive paths, or NUL bytes.
WithOpenFileFlagMapper(mapper) OpenFile receives the exact flags supplied by x/net/webdav. mapper(normalizedName, flag) can rewrite flags or return an error before the underlying afero.Fs.OpenFile call. Multiple mappers run in option order.
WithObjectStoreWriteMode() PUT/COPY destinations are opened as O_RDWR|O_CREATE|O_TRUNC; non-create property writes may open as O_RDWR. Create/write opens using O_RDWR are rewritten to O_WRONLY; non-create O_RDWR opens are downgraded to O_RDONLY. This helps S3/R2-like backends that do not support read-write object handles.
WithSyntheticWriteStat() If a writable file's Stat() fails during PUT, the WebDAV PUT fails before Close(). Writable handles track bytes written and return synthetic FileInfo when the underlying Stat() fails. Existing successful Stat() results still win.
WithStatFallback(fallback) Stat returns the underlying filesystem error. fallback(ctx, normalizedName, originalErr) may return replacement FileInfo, or decline and keep the original error.
WithImplicitDirectoryStat(stat) A missing path is missing, even if object keys exist under that prefix. When the underlying Stat reports not-exist, stat(normalizedName) may synthesize directory FileInfo, allowing object-store prefixes to appear as WebDAV directories.
WithRecursiveMkdir() Mkdir creates exactly one path segment and fails when parents are missing. Mkdir delegates to MkdirAll, so WebDAV MKCOL can create missing parent prefixes.

Example object-store-oriented setup:

handler := &webdav.Handler{
    FileSystem: aferodav.NewFS(
        objectFS,
        aferodav.WithPathCleaner(strictCleanPath),
        aferodav.WithObjectStoreWriteMode(),
        aferodav.WithSyntheticWriteStat(),
        aferodav.WithImplicitDirectoryStat(statImplicitPrefix),
        aferodav.WithRecursiveMkdir(),
    ),
    LockSystem: webdav.NewMemLS(),
}

Running the example

# Serve an in-memory afero.Fs over WebDAV
go run ./example -mode afero-to-webdav -addr :8080

# Use a webdav.FileSystem as an afero.Fs (writes a file, then serves via HTTP)
go run ./example -mode webdav-to-afero -addr :8080

Testing

go test ./...

License

MIT

Documentation

Overview

Package aferodav provides bidirectional adapters between golang.org/x/net/webdav.FileSystem and github.com/spf13/afero.Fs.

Usage:

var wdfs webdav.FileSystem = myWebDAVBackend()
var afs  afero.Fs         = aferodav.New(wdfs, context.Background())

// now use afs with any afero-aware library
data, _ := afero.ReadFile(afs, "/notes/hello.txt")

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func New

func New(fs webdav.FileSystem, ctx context.Context) afero.Fs

New wraps a webdav.FileSystem as an afero.Fs. The supplied context is used for every WebDAV call; pass context.Background() if you have no request-scoped context.

func NewFS

func NewFS(fs afero.Fs, opts ...FSOption) webdav.FileSystem

NewFS wraps an afero.Fs as a webdav.FileSystem, so it can be used directly as the FileSystem field of a webdav.Handler.

Without options, NewFS keeps webdav.FileSystem and os package semantics. Pass FSOption values to opt into convenience or object-store compatibility behavior such as parent directory creation, path validation, OpenFile flag rewriting, synthetic write Stat metadata, implicit directory Stat metadata, or recursive Mkdir.

Usage:

afs := afero.NewMemMapFs()
handler := &webdav.Handler{
    FileSystem: aferodav.NewFS(afs),
    LockSystem: webdav.NewMemLS(),
}

Types

type FSOption

type FSOption interface {
	// contains filtered or unexported methods
}

FSOption configures NewFS. Options are additive and opt-in; without options, NewFS preserves standard webdav.FileSystem and os package behavior.

func WithAutoMkdirParents

func WithAutoMkdirParents() FSOption

WithAutoMkdirParents changes create and rename behavior for NewFS.

Default result: OpenFile with O_CREATE and Rename fail when the destination parent directory is missing.

Enabled result: missing parent directories are created with MkdirAll before create or rename. This is convenient for clients that upload nested paths, but it is not enabled by default because webdav.FileSystem methods are expected to follow os package semantics.

func WithImplicitDirectoryStat added in v0.2.0

func WithImplicitDirectoryStat(stat ImplicitDirectoryStat) FSOption

WithImplicitDirectoryStat changes missing-path Stat behavior for NewFS.

Default result: a missing path remains missing, even if an object-store backend has objects below that prefix.

Enabled result: when the underlying Stat reports os.ErrNotExist, stat may synthesize directory FileInfo for the normalized path. Use this to represent object-store prefixes as WebDAV directories when listing shows child objects.

func WithObjectStoreWriteMode added in v0.2.0

func WithObjectStoreWriteMode() FSOption

WithObjectStoreWriteMode changes OpenFile flags for object-store-like backends.

Default result: PUT and COPY destinations from x/net/webdav are usually opened as O_RDWR|O_CREATE|O_TRUNC, and property updates may open existing resources as O_RDWR.

Enabled result:

  • O_RDWR opens with O_CREATE become O_WRONLY opens;
  • non-create O_RDWR opens become read-only opens.

This supports S3/R2/GCS-style backends that can stream writes or read objects, but cannot provide a read-write object handle. It is opt-in because normal filesystems support O_RDWR and should keep standard os.OpenFile semantics.

func WithOpenFileFlagMapper added in v0.2.0

func WithOpenFileFlagMapper(mapper OpenFileFlagMapper) FSOption

WithOpenFileFlagMapper changes OpenFile flag handling for NewFS.

Default result: OpenFile receives the exact flags supplied by x/net/webdav.

Enabled result: mapper can rewrite flags or reject the open before NewFS calls afero.Fs.OpenFile. Multiple mappers run in option order.

func WithPathCleaner added in v0.2.0

func WithPathCleaner(cleaner PathCleaner) FSOption

WithPathCleaner changes path normalization behavior for NewFS.

Default result: paths are normalized with path.Clean, so a path such as "/a/../b" becomes "/b".

Enabled result: cleaner runs first and sees the raw path before path.Clean. It can reject or rewrite paths before normalization hides details such as "..", backslashes, Windows drive paths, or NUL bytes.

func WithRecursiveMkdir added in v0.2.0

func WithRecursiveMkdir() FSOption

WithRecursiveMkdir changes NewFS.Mkdir behavior.

Default result: Mkdir creates exactly one path segment and fails when parent directories are missing.

Enabled result: Mkdir delegates to afero.Fs.MkdirAll, so WebDAV MKCOL can create missing parent prefixes. This is intended for backends where directories are implicit prefixes rather than real filesystem entries.

func WithStatFallback added in v0.2.0

func WithStatFallback(fallback StatFallback) FSOption

WithStatFallback changes NewFS.Stat behavior after the underlying filesystem returns an error.

Default result: Stat returns the underlying afero.Fs.Stat error.

Enabled result: fallback receives the normalized path and original error and may return replacement FileInfo. Return handled=false to keep the original error.

func WithSyntheticWriteStat added in v0.2.0

func WithSyntheticWriteStat() FSOption

WithSyntheticWriteStat changes Stat behavior on writable files returned by NewFS.

Default result: if a writable file's Stat fails during PUT, x/net/webdav fails the request before closing the file.

Enabled result: writable handles track bytes written and return synthetic FileInfo when the underlying file's Stat fails. Successful underlying Stat results still take precedence. This helps object-store streaming writes where metadata may not be available until Close.

type ImplicitDirectoryStat added in v0.2.0

type ImplicitDirectoryStat func(name string) (info os.FileInfo, handled bool, err error)

ImplicitDirectoryStat can synthesize directory metadata for object stores that do not have real directory objects. The name argument is already normalized. Return handled=false when the path should still be treated as missing.

type OpenFileFlagMapper added in v0.2.0

type OpenFileFlagMapper func(name string, flag int) (int, error)

OpenFileFlagMapper rewrites the flags passed to afero.Fs.OpenFile. The name argument is already normalized to absolute slash-separated form. Return an error to reject the open before it reaches the underlying filesystem.

type PathCleaner added in v0.2.0

type PathCleaner func(op PathOp, name string) (string, error)

PathCleaner validates or rewrites a WebDAV path before aferodav applies its built-in path.Clean normalization. The name argument is the raw WebDAV path received by NewFS; return an error to reject it, or a replacement path to continue.

type PathOp added in v0.2.0

type PathOp = string

PathOp identifies which NewFS method is validating a path. The constants in this package cover every path-bearing webdav.FileSystem method.

const (
	// PathOpMkdir identifies a path passed to webdav.FileSystem.Mkdir.
	PathOpMkdir PathOp = "mkdir"
	// PathOpOpenFile identifies a path passed to webdav.FileSystem.OpenFile.
	PathOpOpenFile PathOp = "openfile"
	// PathOpRemoveAll identifies a path passed to webdav.FileSystem.RemoveAll.
	PathOpRemoveAll PathOp = "removeall"
	// PathOpRenameSource identifies the source path passed to webdav.FileSystem.Rename.
	PathOpRenameSource PathOp = "rename-source"
	// PathOpRenameDestination identifies the destination path passed to webdav.FileSystem.Rename.
	PathOpRenameDestination PathOp = "rename-destination"
	// PathOpStat identifies a path passed to webdav.FileSystem.Stat.
	PathOpStat PathOp = "stat"
)

type StatFallback added in v0.2.0

type StatFallback func(ctx context.Context, name string, err error) (info os.FileInfo, handled bool, fallbackErr error)

StatFallback can synthesize a FileInfo when the underlying afero.Fs.Stat fails. The name argument is already normalized. Return handled=false to leave the original Stat error in place.

Directories

Path Synopsis
Example showing both adapter directions.
Example showing both adapter directions.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL