Skip to content

v0.9.3

Choose a tag to compare

@github-actions github-actions released this 17 Aug 01:54
· 410 commits to main since this release

Fixed

  • A str destination to rename()/symlink_to() was re-parsed as a URI,
    so part of it was silently discarded.
    Every scheme resolved the
    destination by feeding it back through the URI parser
    (Uri(self.parent, target) for rename(), type(self)(target) inside
    Path.symlink_to()). That reads a decoded filesystem path as URI
    syntax: everything from a ? or # onward became a query/fragment and was
    dropped, %xx was percent-decoded, and a relative destination whose first
    segment ended in : was read as a scheme. Measured against a real SFTP
    server (TrueNAS 26.0.0-BETA.1):

    call file/link actually produced
    rename(".../rn?b.txt") .../rn
    rename(".../rn%20b.txt") .../rn b.txt
    symlink_to(".../cache?v=2") link points at .../cache
    rename("C:/Temp/x.txt") /Temp/x.txt (C: taken as a scheme)

    Nothing raised. When something already occupied the truncated name the call
    instead failed with a bare OSError: Failure, so the symptom was either
    silent misplacement or an unexplained error depending on what happened to
    be there. Downstream, pytruenas's documented
    client.path(x).symlink_to(y) route created a wrong link, and
    PathSyncer's symlink_mode="preserve" (which hands symlink_to() the
    raw target string readlink() returned) mirrored such a link to the wrong
    place.

    A str destination is now taken as an already-decoded path — ?, #,
    % and : are ordinary filename characters — via the new
    Uri._from_decoded_path(), one implementation shared by
    Uri._rename_target() (used by SftpPath, FtpPath, DavPath,
    S3Path, GsPath, AzPath and ArchiveUri) and by
    UriPath._symlink_target(), an override of a new Path._symlink_target()
    hook. Relative destinations keep their existing meaning: a rename()
    destination is a sibling, a symlink_to() target is stored verbatim and
    stays relative. The string is not percent-encoded on the way in, so a
    destination that legitimately contains a literal %20 — or a path object
    built by a consumer that already encoded it — is not encoded twice.
    readlink(), unlink(), rmdir() and hardlink_to() never had this
    defect. copy()/move() are deliberately unchanged: their str
    destination is still parsed as a URI, which is what makes a cross-scheme
    copy("s3://bucket/key") work. See docs/divergences.md.

Full Changelog: v0.9.2...v0.9.3