Skip to content

Commit aaa305f

Browse files
committed
fix(l10n): Fix language selection
Signed-off-by: Joas Schilling <coding@schilljs.com>
1 parent cde64a3 commit aaa305f

File tree

2 files changed

+42
-5
lines changed

2 files changed

+42
-5
lines changed

lib/private/L10N/Factory.php

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -133,10 +133,7 @@ public function __construct(
133133
public function get($app, $lang = null, $locale = null) {
134134
return new LazyL10N(function () use ($app, $lang, $locale) {
135135
$app = \OC_App::cleanAppId($app);
136-
if ($lang !== null) {
137-
$lang = str_replace(['\0', '/', '\\', '..'], '', $lang);
138-
}
139-
136+
$lang = $this->cleanLanguage($lang);
140137
$forceLang = $this->config->getSystemValue('force_language', false);
141138
if (is_string($forceLang)) {
142139
$lang = $forceLang;
@@ -169,6 +166,29 @@ public function get($app, $lang = null, $locale = null) {
169166
});
170167
}
171168

169+
/**
170+
* Remove some invalid characters before using a string as a language
171+
*
172+
* @psalm-taint-escape callable
173+
* @psalm-taint-escape cookie
174+
* @psalm-taint-escape file
175+
* @psalm-taint-escape has_quotes
176+
* @psalm-taint-escape header
177+
* @psalm-taint-escape html
178+
* @psalm-taint-escape include
179+
* @psalm-taint-escape ldap
180+
* @psalm-taint-escape shell
181+
* @psalm-taint-escape sql
182+
* @psalm-taint-escape unserialize
183+
*/
184+
private function cleanLanguage(?string $lang): ?string {
185+
if ($lang === null) {
186+
return null;
187+
}
188+
$lang = preg_replace('/[^a-zA-Z0-9.;,=-]/', '', $lang);
189+
return str_replace('..', '', $lang);
190+
}
191+
172192
/**
173193
* Find the best language
174194
*
@@ -478,7 +498,7 @@ public function localeExists($locale) {
478498
* @throws LanguageNotFoundException
479499
*/
480500
private function getLanguageFromRequest(?string $app = null): string {
481-
$header = $this->request->getHeader('ACCEPT_LANGUAGE');
501+
$header = $this->cleanLanguage($this->request->getHeader('ACCEPT_LANGUAGE'));
482502
if ($header !== '') {
483503
$available = $this->findAvailableLanguages($app);
484504

tests/lib/L10N/FactoryTest.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,23 @@ protected function getFactory(array $methods = [], $mockRequestGetHeaderMethod =
8484
return new Factory($this->config, $this->request, $this->userSession, $this->cacheFactory, $this->serverRoot);
8585
}
8686

87+
public static function dataCleanLanguage(): array {
88+
return [
89+
'null shortcut' => [null, null],
90+
'default language' => ['de', 'de'],
91+
'malicious language' => ['de/../fr', 'defr'],
92+
'request language' => ['kab;q=0.8,ka;q=0.7,de;q=0.6', 'kab;q=0.8,ka;q=0.7,de;q=0.6'],
93+
];
94+
}
95+
96+
/**
97+
* @dataProvider dataCleanLanguage
98+
*/
99+
public function testCleanLanguage(?string $lang, ?string $expected): void {
100+
$factory = $this->getFactory();
101+
$this->assertSame($expected, self::invokePrivate($factory, 'cleanLanguage', [$lang]));
102+
}
103+
87104
public function dataFindAvailableLanguages(): array {
88105
return [
89106
[null],

0 commit comments

Comments
 (0)