See: story index
Files: src/os/transport.h, src/os/unix/transport.cpp
The foundation. Define the PWSTransport ABI, write the plugin loader, and hook pws_os::FOpen / FClose in file.cpp. No actual network code yet — a URL at this point would load a plugin and call fetch, but there was no WebDAV plugin to load.
Key decisions made here: single-fd TOCTOU prevention, RFC 3986 scheme validation, cache dir at ~/.cache/pwsafe/, FILE*-keyed cache map.
Files: src/os/plugins/file/transport-file.cpp, src/os/unix/file.cpp
A file:///path/to/db.psafe3 URL now works end-to-end. The plugin just copies the file in and out. More importantly this tested the entire intercept machinery before adding real network code.
Also added: LockFile, UnlockFile, IsLockedFile in file.cpp — the lock plumbing that would later call the daemon.
First bug found: on a colon-free path, url.find(':') returns npos. On 64-bit, npos < 2 is false (npos is size_t(-1)), so the old check would have returned the entire path as the scheme. Fixed by adding pos == npos as the first guard.
Files: src/ui/wxWidgets/DbSelectionPanel.h/.cpp
The 'Open database' panel used a wxFilePickerCtrl — a widget that only accepts local file paths via the OS file dialog. Replaced with a wxComboBox (free text + dropdown history) plus a 'Browse…' button that opens wxFileDialog for local files.
DoValidation updated to check pws_is_transport_url(path) first. If true, calls pws_find_transport() to verify a plugin is available, then accepts the URL as-is. Local paths go through the original wxFileName validation path.
File: src/os/plugins/webdav/transport-webdav.cpp
libcurl drives all five operations: fetch (GET → cache file with O_CREAT 0600 permissions), store (PUT with If: (<token>) header when lock held per RFC 4918 §10.4.1), exists (HEAD), lock (OPTIONS probe for DAV:2 first; 5-minute timeout; stores token in s_lock_tokens), unlock (looks up token from map).
A shared make_curl() helper sets protocol restrictions, SSL verification, redirect policy, and timeouts uniformly — no per-operation curl configuration drift.
First live smoke test: opened https://webdav.critchley.biz/test/test2.psafe3 successfully.
File: src/os/unix/transport_lockd.cpp
The hardest phase. WebDAV LOCK/UNLOCK calls libcurl, which is not async-signal-safe. pwsafe's UnlockFile is called from signal handlers and destructors. Solution: fork a child process on the first lock acquisition; the child holds the lock token and handles all lock/unlock/store over a Unix socketpair. write(2) is async-signal-safe; the parent only ever writes to the socket.
Also fixed: after fork(), the child has its own copy of s_lock_tokens. If the parent called t->store() directly, it checked its own empty map, omitted If: (<token>), and got HTTP 423. Fix: FClose routes store through the daemon when pws_has_lock() is true.
Also fixed: IsLockedFile for transport URLs — now calls pws_has_lock(url) (in-process registry) instead of FileExists(url + ".plk") (WebDAV HEAD → 404 → always false).
Full details: 04-lockd
Files: OpenUrlDlg.h/.cpp, MenuFileHandlers.cpp, PasswordSafeFrame.h/.cpp
File → Open URL… opens a dialog with an editable wxComboBox pre-filled with URL history (stored in wxConfig under /URLHistory/url0…url9, newest-first, max 10 entries).
Clicking OK calls the existing Open(path) method — which calls SetCurFile → ReadCurFile → pws_os::FOpen — so the entire transport intercept fires exactly as if the user had typed the URL in DbSelectionPanel.