Fix WebDAV upload of non-ASCII/special filenames (URL + MOVE header encoding)
Build & Release APK / build (push) Successful in 12m50s
Build & Release APK / build (push) Successful in 12m50s
Volume test (100 files) surfaced it: files with non-ASCII names (e.g. 'naïve café.txt') failed to upload — url() built a raw string, so the MOVE Destination header carried non-ASCII chars that OkHttp rejects. Now url() percent-encodes each path segment via HttpUrl.addPathSegments (also covers '&', spaces, CJK). Regression test specialAndNonAsciiNames_upload added.
This commit is contained in:
@@ -236,7 +236,24 @@ class FullSyncEngineTest {
|
|||||||
assertEquals("newer local must win", "local-newer", remoteText("$remote/n.txt"))
|
assertEquals("newer local must win", "local-newer", remoteText("$remote/n.txt"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── 13. Content integrity: binary-ish bytes round-trip exactly ────────────
|
// ── 13b. Special & non-ASCII filenames upload (WebDAV URL/header encoding) ─
|
||||||
|
@Test fun specialAndNonAsciiNames_upload() = runBlocking {
|
||||||
|
val (pair, local, remote) = newPair("special", SyncDirection.UPLOAD_ONLY, DeleteBehavior.KEEP)
|
||||||
|
write(local, "naïve café.txt", "accents") // non-ASCII (broke MOVE Destination header)
|
||||||
|
write(local, "a&b (1).txt", "ampersand") // & ( ) space
|
||||||
|
write(local, "日本語.txt", "cjk") // multibyte unicode
|
||||||
|
write(local, "my photo.txt", "space")
|
||||||
|
val r = sync(pair)
|
||||||
|
assertEquals("all special-name files must upload", 4, r.uploaded)
|
||||||
|
assertEquals(0, r.failedFiles)
|
||||||
|
val names = remoteNames(remote)
|
||||||
|
assertTrue("naïve café.txt" in names)
|
||||||
|
assertTrue("a&b (1).txt" in names)
|
||||||
|
assertTrue("日本語.txt" in names)
|
||||||
|
assertTrue("my photo.txt" in names)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── 14. Content integrity: binary-ish bytes round-trip exactly ────────────
|
||||||
@Test fun contentIntegrity_roundTrip() = runBlocking {
|
@Test fun contentIntegrity_roundTrip() = runBlocking {
|
||||||
val (pair, local, remote) = newPair("integrity", SyncDirection.TWO_WAY)
|
val (pair, local, remote) = newPair("integrity", SyncDirection.TWO_WAY)
|
||||||
val payload = (0..5000).joinToString("") { "Ω$it·" }
|
val payload = (0..5000).joinToString("") { "Ω$it·" }
|
||||||
|
|||||||
@@ -181,7 +181,13 @@ open class WebDavProvider(protected val account: CloudAccount) : CloudProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected fun url(path: String) = "$baseUrl/${path.trimStart('/')}"
|
// Build a properly percent-encoded URL. addPathSegments encodes each segment (spaces,
|
||||||
|
// ampersands, and — critically — non-ASCII like "café"), which keeps OkHttp from rejecting
|
||||||
|
// non-ASCII in the WebDAV MOVE "Destination" header and avoids malformed request URLs.
|
||||||
|
protected fun url(path: String): String {
|
||||||
|
val base = baseUrl.toHttpUrlOrNull() ?: return "$baseUrl/${path.trimStart('/')}"
|
||||||
|
return base.newBuilder().addPathSegments(path.trimStart('/')).build().toString()
|
||||||
|
}
|
||||||
|
|
||||||
private fun parsePropfind(xml: String, parentPath: String, dropFirst: Boolean = true): List<RemoteFile> {
|
private fun parsePropfind(xml: String, parentPath: String, dropFirst: Boolean = true): List<RemoteFile> {
|
||||||
val results = mutableListOf<RemoteFile>()
|
val results = mutableListOf<RemoteFile>()
|
||||||
|
|||||||
Reference in New Issue
Block a user