Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
80.43% covered (warning)
80.43%
37 / 46
42.86% covered (danger)
42.86%
3 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
JavaHelper
80.43% covered (warning)
80.43%
37 / 46
42.86% covered (danger)
42.86%
3 / 7
23.00
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 init
80.00% covered (warning)
80.00%
4 / 5
0.00% covered (danger)
0.00%
0 / 1
3.07
 initializeUtf8Locale
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
3
 getUtf8LocaleCandidates
71.43% covered (warning)
71.43%
10 / 14
0.00% covered (danger)
0.00%
0 / 1
4.37
 restoreOriginalLocaleEnvironment
80.00% covered (warning)
80.00%
4 / 5
0.00% covered (danger)
0.00%
0 / 1
4.13
 isNonUTF8Locale
40.00% covered (danger)
40.00%
2 / 5
0.00% covered (danger)
0.00%
0 / 1
4.94
 getJavaPath
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2
3declare(strict_types=1);
4/**
5 * SPDX-FileCopyrightText: 2025 LibreCode coop and contributors
6 * SPDX-FileCopyrightText: 2023 Nextcloud GmbH <https://github.com/nextcloud/server>
7 * SPDX-License-Identifier: AGPL-3.0-or-later
8 */
9
10namespace OCA\Libresign\Helper;
11
12use OCA\Libresign\AppInfo\Application;
13use OCP\IAppConfig;
14use OCP\IL10N;
15use Psr\Log\LoggerInterface;
16
17class JavaHelper {
18    private bool $isInitialized = false;
19    public function __construct(
20        protected IAppConfig $appConfig,
21        protected IL10N $l10n,
22        protected LoggerInterface $logger,
23    ) {
24    }
25
26    public function init(): void {
27        if ($this->isInitialized) {
28            return;
29        }
30        if ($this->isNonUTF8Locale()) {
31            $this->initializeUtf8Locale();
32        }
33        $this->isInitialized = true;
34    }
35
36    private function initializeUtf8Locale(): void {
37        $originalLang = getenv('LANG');
38        $originalLocale = setlocale(LC_CTYPE, '0');
39        $attemptedLocales = $this->getUtf8LocaleCandidates();
40
41        foreach ($attemptedLocales as $locale) {
42            putenv('LANG=' . $locale);
43            setlocale(LC_CTYPE, $locale, 'UTF-8');
44            if (!$this->isNonUTF8Locale()) {
45                return;
46            }
47        }
48
49        $this->restoreOriginalLocaleEnvironment($originalLang, $originalLocale);
50        $this->logger->warning(sprintf(
51            'JavaHelper: setlocale did not work properly after attempting locales: %s',
52            implode(', ', $attemptedLocales)
53        ));
54    }
55
56    /**
57     * @return list<string>
58     */
59    private function getUtf8LocaleCandidates(): array {
60        $localeCode = trim(str_replace('-', '_', $this->l10n->getLocaleCode()));
61        $candidates = [];
62
63        if ($localeCode !== '') {
64            if (preg_match('/^[A-Za-z]{2,3}_[A-Za-z]{2}$/', $localeCode) === 1) {
65                [$language, $region] = explode('_', $localeCode, 2);
66                $candidates[] = sprintf('%s_%s.UTF-8', strtolower($language), strtoupper($region));
67                $candidates[] = sprintf('%s_%s.utf8', strtolower($language), strtoupper($region));
68            } elseif (preg_match('/^[A-Za-z]{2,3}$/', $localeCode) === 1) {
69                $language = strtolower($localeCode);
70                $candidates[] = sprintf('%s_%s.UTF-8', $language, strtoupper($language));
71                $candidates[] = $language . '.UTF-8';
72            }
73        }
74
75        $candidates[] = 'C.UTF-8';
76        $candidates[] = 'UTF-8';
77
78        return array_values(array_unique($candidates));
79    }
80
81    private function restoreOriginalLocaleEnvironment(string|false $originalLang, string|false $originalLocale): void {
82        if ($originalLang === false) {
83            putenv('LANG');
84        } else {
85            putenv('LANG=' . $originalLang);
86        }
87
88        if (is_string($originalLocale) && $originalLocale !== '') {
89            setlocale(LC_CTYPE, $originalLocale);
90        }
91    }
92
93    /**
94     * Check if current locale is non-UTF8
95     *
96     * Based on: https://github.com/nextcloud/server/blob/cf1eed2769d928f4a7fe4543d51994331701f2d9/lib/private/legacy/OC_Util.php#L692-L705
97     *
98     * @return bool
99     */
100    protected function isNonUTF8Locale(): bool {
101        if (function_exists('escapeshellcmd')) {
102            return escapeshellcmd('§') === '';
103        }
104        if (function_exists('escapeshellarg')) {
105            return escapeshellarg('§') === '\'\'';
106        }
107        return preg_match('/utf-?8/i', setlocale(LC_CTYPE, '0')) === 0;
108    }
109
110    /**
111     * Returns the configured Java binary path.
112     * Initializes the environment if needed.
113     */
114    public function getJavaPath(): string {
115        if (!$this->isInitialized) {
116            $this->init();
117        }
118        return $this->appConfig->getValueString(Application::APP_ID, 'java_path');
119    }
120}