Skip to content

refactor(core): remove whitespace trimming from path normalization and rename - #7801

Open
erickguan wants to merge 2 commits into
mainfrom
path-norm
Open

refactor(core): remove whitespace trimming from path normalization and rename#7801
erickguan wants to merge 2 commits into
mainfrom
path-norm

Conversation

@erickguan

@erickguan erickguan commented Jun 19, 2026

Copy link
Copy Markdown
Member

Which issue does this PR close?

Implements RFC 7799's path normalization part.

Also closes #6577

Rationale for this change

  • Improve readability and documentation
  • More defined path normalization conventions, rules, and who should use what.

What changes are included in this PR?

  • Remove .trim() from normalize_path to preserve whitespace in path components, aligning with POSIX, URI, and object-store conventions where whitespace is significant content.
  • Add . segment filtering to both normalize_path and normalize_root so that ./a/./b normalizes to a/b.
  • Introduce build_absolute_path, build_rooted_absolute_path, and build_relative_path as the canonical names; deprecate the old build_abs_path, build_rooted_abs_path, build_rel_path aliases.
  • Use doctests when possible
  • Update services to use the new function names.

Are there any user-facing changes?

No

AI Usage Statement

GPT-5.5 with codex implements some code.

@erickguan erickguan changed the title WIP refactor(core): remove whitespace trimming from path normalization refactor(core): remove whitespace trimming from path normalization and rename Jun 21, 2026
@erickguan
erickguan marked this pull request as ready for review June 21, 2026 12:23
@erickguan
erickguan requested a review from Xuanwo as a code owner June 21, 2026 12:23
@dosubot dosubot Bot added size:XL This PR changes 500-999 lines, ignoring generated files. releases-note/refactor The PR does a refactor on code or has a title that begins with "refactor" labels Jun 21, 2026
erickguan pushed a commit that referenced this pull request Aug 15, 2026
#8070)

* fix(services/ipmfs): stop stripping the root twice from listed entries

The listing loop concatenates the path being listed with a files/ls
name, then runs the result through build_rel_path a second time:

    let path = match object.mode() {
        EntryMode::FILE => format!("{}{}", self.path, object.name),
        ...
    };
    let path = build_rel_path(&self.root, &path);

self.path is what the operator handed the service, so it is already
relative to the root -- IpmfsCore::ipmfs_ls is what turns it into a
rooted absolute path for the request. The concatenation is therefore
root-relative before that call, and the call removes a prefix that is
not there.

Under the default root "/" the second strip happens to cancel out, which
is why this has gone unnoticed. Under any other root, in release:

    root "/abc/", listing "dir/", name "a"
        build_rel_path("/abc/", "dir/a") -> "a"
        the directory is dropped from every entry path

    root "/abc/", listing the root itself (self.path is "/"), name "a"
        build_rel_path("/abc/", "/a")
        panicked: start byte index 5 is out of bounds for string of
        length 2

In a debug build both cases trip the debug_assert! inside build_rel_path
instead.

The construction moves into build_entry_path, which takes no root at
all -- an entry path does not depend on one. The only thing it has to
handle is that self.path is "/" when the root itself is listed, and an
entry path carries no leading slash.

The self-entry twenty lines above is left alone: it does
build_abs_path then build_rel_path, a deliberate round trip, and is
correct.

Three unit tests. Putting the old expression back passes all of them
with a root of "/" and fails three of them with a root of "/abc/" --
which is the shape of the bug.

Note for rebasing: #7801 renames this very call to
build_relative_path. If that lands first this needs a one-word rebase;
the call itself goes away here.

* Inline the entry-path construction and drop the test module

Matching the shape asked for on #8068, #8069 and #8071 rather than
waiting to be asked again: the extracted helper goes, the construction
is inline, and the test module goes with it.

The fix is unchanged -- the concatenation is already relative to the
root, so the second build_rel_path is what truncated it, and "/" as the
listed path must not become a leading slash on the entry.

* Drop the inline comment
erickguan pushed a commit that referenced this pull request Aug 15, 2026
… bodies (#8079)

* fix(services/dbfs): root the put path and stop encoding paths in JSON bodies

Two defects in how this service addresses paths, both in core.rs.

The path in the dbfs/put body is not rooted:

    let req_body = &json!({
        "path": path,

Every sibling roots it first -- create_dir at :60, delete at :89,
rename at :116, list at :142, get-status at :193 all call
build_rooted_abs_path. The value arrives from DbfsBackend::write as the
operator-relative path and nothing else applies the root, so with any
non-default root a write and the stat, list or delete that follows it
address different DBFS paths.

And four values are percent-encoded inside JSON request bodies:

    "path": percent_encode_path(&p),                     // :65, :94
    "source_path": percent_encode_path(&source),         // :126
    "destination_path": percent_encode_path(&target),    // :127

Nothing URL-decodes a JSON string value. The genuine query strings at
:149 and :200 are encoded, and the server does decode those -- so the
two halves of this file disagree with each other. create_dir("my dir/")
creates a directory literally named my%20dir, while the get-status that
follows asks for "my dir" and gets NotFound. For delete it is worse than
an error: deleter.rs notes the server answers 200 even when the path
does not exist, so the call reports success having removed nothing.

A repo-wide grep for percent_encode_path inside json! across
core/services matches these four lines and nothing else; dbfs is the
only service in the tree that does it.

Neither change affects an ordinary path. percent_encode_path leaves
A-Z a-z 0-9 / - _ . ! ~ * ' ( ) untouched, so a plain key serialises
identically; only a path containing a space, %, #, ?, &, +, ,, :, =, @
or a non-ASCII byte changes, and those are the ones broken today.

One test, on the only one of the four that is a synchronous request
builder. The other three send inside async methods, so pinning them
would mean restructuring; their evidence is the in-file inconsistency
above. Reverting the rooting fails that test and nothing else.

The lister emits root-prefixed entry paths as well -- that is a separate
change to a separate file and is not in here.

Note for rebasing: #7801 renames build_rooted_abs_path in this file. If
it lands first the new call in dbfs_create_file_request needs the new
name.

* Remove the test module
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

releases-note/refactor The PR does a refactor on code or has a title that begins with "refactor" size:XL This PR changes 500-999 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug: fail to read files with trailing whitespaces

1 participant