See parent: pwsafe | See also: webdav design
Design for making remote storage (WebDAV, S3, etc.) a dynamically loadable plugin. The main pwsafe binary gains a small transport dispatch layer; each remote store is a separate .so that registers itself at load time. Zero changes to crypto, record-parsing, or UI layers.
pwsafe already has a cross-platform dynamic library abstraction in src/os/lib.h: pws_os::LoadLibrary, pws_os::GetFunction, pws_os::FreeLibrary. These wrap dlopen/dlsym on Unix/Mac and LoadLibrary/GetProcAddress on Windows. The cross-platform boilerplate is already solved.
A new src/os/transport.h defines the plugin interface as a plain C struct (for ABI stability across compilers):
typedef struct {
int abi_version; // set to PWSTransport_ABI_VERSION
const char *scheme; // e.g. "http", "https", "s3"
int (*fetch )(const char *url, const char *local_path);
int (*store )(const char *local_path, const char *url);
int (*exists)(const char *url);
int (*lock )(const char *url, char *token_out, size_t len);
int (*unlock)(const char *url, const char *token);
void (*cleanup)(void);
} PWSTransport;
A new src/os/transport.cpp provides:
• pws_register_transport(PWSTransport*) — called by plugins to register a scheme
• pws_find_transport(const char *url) — returns loaded transport for scheme, or null
• Plugin loader — lazy: triggered on first URL open, not at startup. Looks up pwsafe-<scheme>.so by name in app binary dir (+ cwd in DEVELOPMENT builds). See ident for full load sequence and lifetime rules.
• Cache path resolver — maps URL to deterministic local path, e.g. ~/.pwsafe/cache/<sha256_of_url>.psafe3
• Offline fallback logic — if fetch fails and cache exists, warn and continue
Modified src/os/unix/file.cpp (and Windows equivalent) — FOpen, FClose, FileExists, LockFile, UnlockFile each call pws_find_transport() first; if a transport is found, delegate to it and work against the local cache file; otherwise fall through to normal local file handling unchanged.
The FILE* map (std::map<FILE*, CacheInfo>) that bridges FOpen and FClose lives in transport.cpp. Plugins never see FILE*; they only handle URLs and local paths.
Each plugin is a standalone shared library that:
• Links its own dependencies (e.g. libcurl for WebDAV; AWS SDK or libcurl for S3)
• Exports a single entry point: void pws_plugin_init(pws_register_fn_t reg)
• Calls reg(&my_transport) for each scheme it handles
• Has zero link dependency on the pwsafe main binary
Example plugins:
• pwsafe-transport-webdav.so — registers http and https; implements WebDAV LOCK/UNLOCK (see webdav design)
• pwsafe-transport-s3.so — registers s3; no locking needed (S3 has no atomic replace either, but single-user use is safe)
• pwsafe-transport-sftp.so — registers sftp; could use libssh2
The plugin calls pws_register_transport() which lives in the main binary. On Linux this works naturally if the main binary is linked with -Wl,--export-dynamic (or the symbol is in a shared lib). On Windows, pass the registration function pointer as a parameter to pws_plugin_init — this is why the init signature takes reg as an argument rather than calling a global directly. This is the portable pattern.
The abi_version field in PWSTransport must be set by the plugin to the value of PWSTransport_ABI_VERSION at compile time. The loader checks this and refuses to load (with a clear error) if versions mismatch. New fields may be appended to the struct; the loader uses abi_version to know which fields are safe to read. This prevents silent crashes when the interface evolves.
Cache management lives entirely in Part 1 (transport.cpp), not in the plugins:
• On open: call fetch(url, cache_path); if it fails and cache exists, offer offline mode
• On save: write to cache_path (normal pwsafe flow); then call store(cache_path, url); if it fails, warn — cache is always consistent
• Backups (incremental .ibak files) are taken against the cache path — no changes to BackupCurFile()
• Locking: if lock() returns a "not supported" code, warn once and proceed
In CMakeLists.txt:
• transport.cpp is compiled into the main binary (or a static lib linked into it)
• Each plugin is a separate add_library(pwsafe-transport-webdav MODULE ...) target
• Plugin targets link their own dependencies; the main binary does not link libcurl
• Plugins are optional: cmake -DWITH_WEBDAV=ON etc.
• ident — embedded identity string format, pre-scan, scheme dispatch rules • file-plugin — file: plugin as reference implementation and test harness • plan — phased implementation plan with open questions
• PWSTransport interface + registry + loader: ~150 lines
• Cache management + offline fallback in transport.cpp: ~150 lines
• Dispatch hooks in file.cpp (Unix + Windows): ~100 lines
• WebDAV plugin (libcurl GET/PUT/HEAD/OPTIONS/LOCK/UNLOCK): ~300 lines
• CMake wiring: ~30 lines
Total: ~730 lines. All remote-storage code is isolated in plugins; adding S3 or SFTP later requires no changes to pwsafe itself.