Skip to content

Commit 822f591

Browse files
author
Vincent Petry
committed
Improved NFD path finding algorithm to apply on each path section
Each path section of the given full path will be checked in each of the NFC/NFD form.
1 parent 543bb2c commit 822f591

2 files changed

Lines changed: 69 additions & 21 deletions

File tree

lib/private/Files/Storage/Wrapper/Encoding.php

Lines changed: 49 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -64,42 +64,70 @@ private function isAscii($str) {
6464
}
6565

6666
/**
67-
* Checks whether the given path exists in NFC or NFD form and returns
68-
* the correct form. If no existing path found, returns the pass as
69-
* it was given.
67+
* Checks whether the given path exists in NFC or NFD form after checking
68+
* each form for each path section and returns the correct form.
69+
* If no existing path found, returns the path as it was given.
70+
*
71+
* @param string $fullPath path to check
72+
*
73+
* @return string original or converted path, or null if none of the forms was found
74+
*/
75+
private function findPathToUse($fullPath) {
76+
$sections = explode('/', $fullPath);
77+
$path = '';
78+
foreach ($sections as $section) {
79+
$path .= $section;
80+
$convertedPath = $this->findPathToUseLastSection($path);
81+
if ($convertedPath === null) {
82+
// no point in continuing if the section was not found, use original path
83+
return $fullPath;
84+
}
85+
$path = $convertedPath . '/';
86+
}
87+
return rtrim($path, '/');
88+
}
89+
90+
/**
91+
* Checks whether the last path section of the given path exists in NFC or NFD form
92+
* and returns the correct form. If no existing path found, returns null.
7093
*
71-
* @param string $path path to check
94+
* @param string $fullPath path to check
7295
*
73-
* @return string original or converted path
96+
* @return string|null original or converted path, or null if none of the forms was found
7497
*/
75-
private function findPathToUse($path) {
76-
if ($path === '' || $this->isAscii($path)) {
77-
return $path;
78-
}
79-
$cachedPath = $this->namesCache[$path];
98+
private function findPathToUseLastSection($fullPath) {
99+
$cachedPath = $this->namesCache[$fullPath];
80100
if ($cachedPath !== null) {
81101
return $cachedPath;
82102
}
83103

84-
$result = $this->storage->file_exists($path);
104+
$sections = explode('/', $fullPath);
105+
$lastSection = array_pop($sections);
106+
$basePath = ltrim(implode('/', $sections) . '/', '/');
107+
108+
if ($lastSection === '' || $this->isAscii($lastSection)) {
109+
return $fullPath;
110+
}
111+
112+
$result = $this->storage->file_exists($fullPath);
85113
if ($result) {
86-
$this->namesCache[$path] = $path;
87-
return $path;
114+
$this->namesCache[$fullPath] = $fullPath;
115+
return $fullPath;
88116
}
89117
// swap encoding
90-
if (\Normalizer::isNormalized($path, \Normalizer::FORM_C)) {
91-
$otherFormPath = \Normalizer::normalize($path, \Normalizer::FORM_D);
118+
if (\Normalizer::isNormalized($lastSection, \Normalizer::FORM_C)) {
119+
$otherFormPath = \Normalizer::normalize($lastSection, \Normalizer::FORM_D);
92120
} else {
93-
$otherFormPath = \Normalizer::normalize($path, \Normalizer::FORM_C);
121+
$otherFormPath = \Normalizer::normalize($lastSection, \Normalizer::FORM_C);
94122
}
95-
if ($this->storage->file_exists($otherFormPath)) {
96-
$this->namesCache[$path] = $otherFormPath;
97-
return $otherFormPath;
123+
if ($this->storage->file_exists($basePath . $otherFormPath)) {
124+
$this->namesCache[$fullPath] = $basePath . $otherFormPath;
125+
return $basePath . $otherFormPath;
98126
}
99127

100128
// return original path, file did not exist at all
101-
$this->namesCache->set[$path] = self::NOT_FOUND;
102-
return $path;
129+
$this->namesCache->set[$fullPath] = self::NOT_FOUND;
130+
return null;
103131
}
104132

105133
/**

tests/lib/files/storage/wrapper/encoding.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,4 +111,24 @@ public function testNfcHigherPriority() {
111111
$this->sourceStorage->file_put_contents(self::NFD_NAME, 'nfd');
112112
$this->assertEquals('nfc', $this->instance->file_get_contents(self::NFC_NAME));
113113
}
114+
115+
public function encodedDirectoriesProvider() {
116+
return [
117+
[self::NFD_NAME, self::NFC_NAME],
118+
[self::NFD_NAME . '/' . self::NFD_NAME, self::NFC_NAME . '/' . self::NFC_NAME],
119+
[self::NFD_NAME . '/' . self::NFC_NAME . '/' .self::NFD_NAME, self::NFC_NAME . '/' . self::NFC_NAME . '/' . self::NFC_NAME],
120+
];
121+
}
122+
123+
/**
124+
* @dataProvider encodedDirectoriesProvider
125+
*/
126+
public function testOperationInsideDirectory($sourceDir, $accessDir) {
127+
$this->sourceStorage->mkdir($sourceDir);
128+
$this->instance->file_put_contents($accessDir . '/test.txt', 'bar');
129+
$this->assertEquals('bar', $this->instance->file_get_contents($accessDir . '/test.txt'));
130+
131+
$this->sourceStorage->file_put_contents($sourceDir . '/' . self::NFD_NAME, 'foo');
132+
$this->assertEquals('foo', $this->instance->file_get_contents($accessDir . '/' . self::NFC_NAME));
133+
}
114134
}

0 commit comments

Comments
 (0)