Skip to content

Distinguish empty path from literal slash in Url.lastSegment - #2797

Open
xhon-pelushi wants to merge 2 commits into
bitfireAT:mainfrom
xhon-pelushi:fix/lastsegment-slash-sentinel
Open

Distinguish empty path from literal slash in Url.lastSegment#2797
xhon-pelushi wants to merge 2 commits into
bitfireAT:mainfrom
xhon-pelushi:fix/lastsegment-slash-sentinel

Conversation

@xhon-pelushi

Copy link
Copy Markdown

Summary

  • Make Url.lastSegment return null for URLs with no path segments instead of "/".
  • Keep returning "/" when the last segment literally decodes to a slash (%2F).
  • Display call sites continue to use ?: "/"; sync/InternalMemberState treat null as missing so a member named / cannot collide with the empty-path sentinel.

Fixes #2782

Test plan

  • ./gradlew :core:testDebugUnitTest --tests 'at.bitfire.davdroid.util.DavUtilsTest'
  • Spot-check collection titles still show / for empty-path homesets.

Return null when a URL has no path segments so a remote member named
\"/\" (decoded from %2F) is not conflated with the empty-path sentinel.
Display sites keep the \"/\" fallback; sync paths treat null as missing.

Fixes bitfireAT#2782
@CLAassistant

CLAassistant commented Aug 12, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@rfc2822 rfc2822 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi,

Thanks for the efforts. Was the concept of this PR done by AI?

There are two additional helpers in dav4jvm:

  • Response.hrefName
  • HttpUtils.fileName

Should we use / unify / drop those?

I think at the end we should have only one method to get the file name from an Url, it it should be always the same.

* otherwise be path separators.
*/
val fileName: String
val fileName: String?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think a member should always have a file name. If it doesn't, it's not an (internal / direct) member.

So InternalMemberState should never be created without a proper file name.

Then all the require would fall away.

Delegate Url.lastSegment to dav4jvm HttpUtils.fileName so empty paths
are "" and a literal "/" segment stays "/". Keep InternalMemberState
fileName non-null by filtering members without a name at construction.

Fixes bitfireAT#2782
@xhon-pelushi

Copy link
Copy Markdown
Author

Thanks for the feedback — pushed a follow-up that unifies this with dav4jvm:

  • Url.lastSegment now delegates to HttpUtils.fileName (same helper behind Response.hrefName())
  • Empty path → ""; literal / segment (e.g. %2F) → "/"
  • Display sites use lastSegment.ifEmpty { "/" }
  • InternalMemberState.fileName is non-null again: members without a file name are filtered out at construction (toInternalMemberStates / sync-collection classify), so the requires around downloads stay for defense only

On the design concept: the goal was specifically to stop conflating “no path” with a resource literally named /, which matches the distinction HttpUtils.fileName already provides. Happy to rename/drop lastSegment in favor of calling HttpUtils.fileName at call sites if you’d rather have only one name in this repo.

@xhon-pelushi

Copy link
Copy Markdown
Author

Thanks @rfc2822 — follow-up commit unifies on HttpUtils.fileName (same as Response.hrefName):

  • InternalMemberState now requires a non-empty file name at construction
  • collection sync skips members without a file name instead of creating invalid state
  • display/title call sites treat an empty last segment as /

I used tooling while exploring the helpers; happy to adjust further if you want lastSegment itself to go away in favor of only HttpUtils.fileName.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Url.lastSegment conflates "empty path" with a literal decoded slash

3 participants