UtilTest.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423
  1. <?php
  2. /**
  3. * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
  4. * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
  5. * SPDX-License-Identifier: AGPL-3.0-or-later
  6. */
  7. namespace Test;
  8. use OC_Util;
  9. /**
  10. * Class UtilTest
  11. *
  12. * @package Test
  13. * @group DB
  14. */
  15. class UtilTest extends \Test\TestCase {
  16. public function testGetVersion() {
  17. $version = \OCP\Util::getVersion();
  18. $this->assertTrue(is_array($version));
  19. foreach ($version as $num) {
  20. $this->assertTrue(is_int($num));
  21. }
  22. }
  23. public function testGetVersionString() {
  24. $version = \OC_Util::getVersionString();
  25. $this->assertTrue(is_string($version));
  26. }
  27. public function testGetEditionString() {
  28. $edition = \OC_Util::getEditionString();
  29. $this->assertTrue(is_string($edition));
  30. }
  31. public function testSanitizeHTML() {
  32. $badArray = [
  33. 'While it is unusual to pass an array',
  34. 'this function actually <blink>supports</blink> it.',
  35. 'And therefore there needs to be a <script>alert("Unit"+\'test\')</script> for it!',
  36. [
  37. 'And It Even May <strong>Nest</strong>',
  38. ],
  39. ];
  40. $goodArray = [
  41. 'While it is unusual to pass an array',
  42. 'this function actually &lt;blink&gt;supports&lt;/blink&gt; it.',
  43. 'And therefore there needs to be a &lt;script&gt;alert(&quot;Unit&quot;+&#039;test&#039;)&lt;/script&gt; for it!',
  44. [
  45. 'And It Even May &lt;strong&gt;Nest&lt;/strong&gt;'
  46. ],
  47. ];
  48. $result = OC_Util::sanitizeHTML($badArray);
  49. $this->assertEquals($goodArray, $result);
  50. $badString = '<img onload="alert(1)" />';
  51. $result = OC_Util::sanitizeHTML($badString);
  52. $this->assertEquals('&lt;img onload=&quot;alert(1)&quot; /&gt;', $result);
  53. $badString = "<script>alert('Hacked!');</script>";
  54. $result = OC_Util::sanitizeHTML($badString);
  55. $this->assertEquals('&lt;script&gt;alert(&#039;Hacked!&#039;);&lt;/script&gt;', $result);
  56. $goodString = 'This is a good string without HTML.';
  57. $result = OC_Util::sanitizeHTML($goodString);
  58. $this->assertEquals('This is a good string without HTML.', $result);
  59. }
  60. public function testEncodePath() {
  61. $component = '/§#@test%&^ä/-child';
  62. $result = OC_Util::encodePath($component);
  63. $this->assertEquals("/%C2%A7%23%40test%25%26%5E%C3%A4/-child", $result);
  64. }
  65. public function testIsNonUTF8Locale() {
  66. // OC_Util::isNonUTF8Locale() assumes escapeshellcmd('§') returns '' with non-UTF-8 locale.
  67. $locale = setlocale(LC_CTYPE, 0);
  68. setlocale(LC_CTYPE, 'C');
  69. $this->assertEquals('', escapeshellcmd('§'));
  70. $this->assertEquals('\'\'', escapeshellarg('§'));
  71. setlocale(LC_CTYPE, 'C.UTF-8');
  72. $this->assertEquals('§', escapeshellcmd('§'));
  73. $this->assertEquals('\'§\'', escapeshellarg('§'));
  74. setlocale(LC_CTYPE, $locale);
  75. }
  76. public function testFileInfoLoaded() {
  77. $expected = function_exists('finfo_open');
  78. $this->assertEquals($expected, \OC_Util::fileInfoLoaded());
  79. }
  80. /**
  81. * Host is "localhost" this is a valid for emails,
  82. * but not for default strict email verification that requires a top level domain.
  83. * So we check that with strict email verification we fallback to the default
  84. */
  85. public function testGetDefaultEmailAddressStrict() {
  86. $email = \OCP\Util::getDefaultEmailAddress("no-reply");
  87. $this->assertEquals('no-reply@localhost.localdomain', $email);
  88. }
  89. /**
  90. * If no strict email check is enabled "localhost" should validate as a valid email domain
  91. */
  92. public function testGetDefaultEmailAddress() {
  93. $config = \OC::$server->getConfig();
  94. $config->setAppValue('core', 'enforce_strict_email_check', 'no');
  95. $email = \OCP\Util::getDefaultEmailAddress("no-reply");
  96. $this->assertEquals('no-reply@localhost', $email);
  97. $config->deleteAppValue('core', 'enforce_strict_email_check');
  98. }
  99. public function testGetDefaultEmailAddressFromConfig() {
  100. $config = \OC::$server->getConfig();
  101. $config->setSystemValue('mail_domain', 'example.com');
  102. $email = \OCP\Util::getDefaultEmailAddress("no-reply");
  103. $this->assertEquals('no-reply@example.com', $email);
  104. $config->deleteSystemValue('mail_domain');
  105. }
  106. public function testGetConfiguredEmailAddressFromConfig() {
  107. $config = \OC::$server->getConfig();
  108. $config->setSystemValue('mail_domain', 'example.com');
  109. $config->setSystemValue('mail_from_address', 'owncloud');
  110. $email = \OCP\Util::getDefaultEmailAddress("no-reply");
  111. $this->assertEquals('owncloud@example.com', $email);
  112. $config->deleteSystemValue('mail_domain');
  113. $config->deleteSystemValue('mail_from_address');
  114. }
  115. public function testGetInstanceIdGeneratesValidId() {
  116. \OC::$server->getConfig()->deleteSystemValue('instanceid');
  117. $instanceId = OC_Util::getInstanceId();
  118. $this->assertStringStartsWith('oc', $instanceId);
  119. $matchesRegex = preg_match('/^[a-z0-9]+$/', $instanceId);
  120. $this->assertSame(1, $matchesRegex);
  121. }
  122. /**
  123. * @dataProvider filenameValidationProvider
  124. */
  125. public function testFilenameValidation($file, $valid) {
  126. // private API
  127. $this->assertEquals($valid, \OC_Util::isValidFileName($file));
  128. // public API
  129. $this->assertEquals($valid, \OCP\Util::isValidFileName($file));
  130. }
  131. public function filenameValidationProvider() {
  132. return [
  133. // valid names
  134. ['boringname', true],
  135. ['something.with.extension', true],
  136. ['now with spaces', true],
  137. ['.a', true],
  138. ['..a', true],
  139. ['.dotfile', true],
  140. ['single\'quote', true],
  141. [' spaces before', true],
  142. ['spaces after ', true],
  143. ['allowed chars including the crazy ones $%&_-^@!,()[]{}=;#', true],
  144. ['汉字也能用', true],
  145. ['und Ümläüte sind auch willkommen', true],
  146. // disallowed names
  147. ['', false],
  148. [' ', false],
  149. ['.', false],
  150. ['..', false],
  151. ['back\\slash', false],
  152. ['sl/ash', false],
  153. ['lt<lt', true],
  154. ['gt>gt', true],
  155. ['col:on', true],
  156. ['double"quote', true],
  157. ['pi|pe', true],
  158. ['dont?ask?questions?', true],
  159. ['super*star', true],
  160. ['new\nline', false],
  161. // better disallow these to avoid unexpected trimming to have side effects
  162. [' ..', false],
  163. ['.. ', false],
  164. ['. ', false],
  165. [' .', false],
  166. // part files not allowed
  167. ['.part', false],
  168. ['notallowed.part', false],
  169. ['neither.filepart', false],
  170. // part in the middle is ok
  171. ['super movie part one.mkv', true],
  172. ['super.movie.part.mkv', true],
  173. ];
  174. }
  175. /**
  176. * Test needUpgrade() when the core version is increased
  177. */
  178. public function testNeedUpgradeCore() {
  179. $config = \OC::$server->getConfig();
  180. $oldConfigVersion = $config->getSystemValue('version', '0.0.0');
  181. $oldSessionVersion = \OC::$server->getSession()->get('OC_Version');
  182. $this->assertFalse(\OCP\Util::needUpgrade());
  183. $config->setSystemValue('version', '7.0.0.0');
  184. \OC::$server->getSession()->set('OC_Version', [7, 0, 0, 1]);
  185. self::invokePrivate(new \OCP\Util, 'needUpgradeCache', [null]);
  186. $this->assertTrue(\OCP\Util::needUpgrade());
  187. $config->setSystemValue('version', $oldConfigVersion);
  188. \OC::$server->getSession()->set('OC_Version', $oldSessionVersion);
  189. self::invokePrivate(new \OCP\Util, 'needUpgradeCache', [null]);
  190. $this->assertFalse(\OCP\Util::needUpgrade());
  191. }
  192. public function testCheckDataDirectoryValidity() {
  193. $dataDir = \OC::$server->getTempManager()->getTemporaryFolder();
  194. touch($dataDir . '/.ocdata');
  195. $errors = \OC_Util::checkDataDirectoryValidity($dataDir);
  196. $this->assertEmpty($errors);
  197. \OCP\Files::rmdirr($dataDir);
  198. $dataDir = \OC::$server->getTempManager()->getTemporaryFolder();
  199. // no touch
  200. $errors = \OC_Util::checkDataDirectoryValidity($dataDir);
  201. $this->assertNotEmpty($errors);
  202. \OCP\Files::rmdirr($dataDir);
  203. $errors = \OC_Util::checkDataDirectoryValidity('relative/path');
  204. $this->assertNotEmpty($errors);
  205. }
  206. protected function setUp(): void {
  207. parent::setUp();
  208. \OC_Util::$scripts = [];
  209. \OC_Util::$styles = [];
  210. self::invokePrivate(\OCP\Util::class, 'scripts', [[]]);
  211. self::invokePrivate(\OCP\Util::class, 'scriptDeps', [[]]);
  212. }
  213. protected function tearDown(): void {
  214. parent::tearDown();
  215. \OC_Util::$scripts = [];
  216. \OC_Util::$styles = [];
  217. self::invokePrivate(\OCP\Util::class, 'scripts', [[]]);
  218. self::invokePrivate(\OCP\Util::class, 'scriptDeps', [[]]);
  219. }
  220. public function testAddScript() {
  221. \OCP\Util::addScript('first', 'myFirstJSFile');
  222. \OCP\Util::addScript('core', 'myFancyJSFile1');
  223. \OCP\Util::addScript('files', 'myFancyJSFile2', 'core');
  224. \OCP\Util::addScript('myApp5', 'myApp5JSFile', 'myApp2');
  225. \OCP\Util::addScript('myApp', 'myFancyJSFile3');
  226. \OCP\Util::addScript('core', 'myFancyJSFile4');
  227. // after itself
  228. \OCP\Util::addScript('core', 'myFancyJSFile5', 'core');
  229. // add duplicate
  230. \OCP\Util::addScript('core', 'myFancyJSFile1');
  231. // dependency chain
  232. \OCP\Util::addScript('myApp4', 'myApp4JSFile', 'myApp3');
  233. \OCP\Util::addScript('myApp3', 'myApp3JSFile', 'myApp2');
  234. \OCP\Util::addScript('myApp2', 'myApp2JSFile', 'myApp');
  235. \OCP\Util::addScript('core', 'common');
  236. \OCP\Util::addScript('core', 'main');
  237. $scripts = \OCP\Util::getScripts();
  238. // Core should appear first
  239. $this->assertEquals(
  240. 0,
  241. array_search('core/js/common', $scripts, true)
  242. );
  243. $this->assertEquals(
  244. 1,
  245. array_search('core/js/main', $scripts, true)
  246. );
  247. $this->assertEquals(
  248. 2,
  249. array_search('core/js/myFancyJSFile1', $scripts, true)
  250. );
  251. $this->assertEquals(
  252. 3,
  253. array_search('core/js/myFancyJSFile4', $scripts, true)
  254. );
  255. // Dependencies should appear before their children
  256. $this->assertLessThan(
  257. array_search('files/js/myFancyJSFile2', $scripts, true),
  258. array_search('core/js/myFancyJSFile3', $scripts, true)
  259. );
  260. $this->assertLessThan(
  261. array_search('myApp2/js/myApp2JSFile', $scripts, true),
  262. array_search('myApp/js/myFancyJSFile3', $scripts, true)
  263. );
  264. $this->assertLessThan(
  265. array_search('myApp3/js/myApp3JSFile', $scripts, true),
  266. array_search('myApp2/js/myApp2JSFile', $scripts, true)
  267. );
  268. $this->assertLessThan(
  269. array_search('myApp4/js/myApp4JSFile', $scripts, true),
  270. array_search('myApp3/js/myApp3JSFile', $scripts, true)
  271. );
  272. $this->assertLessThan(
  273. array_search('myApp5/js/myApp5JSFile', $scripts, true),
  274. array_search('myApp2/js/myApp2JSFile', $scripts, true)
  275. );
  276. // No duplicates
  277. $this->assertEquals(
  278. $scripts,
  279. array_unique($scripts)
  280. );
  281. // All scripts still there
  282. $scripts = [
  283. "core/js/common",
  284. "core/js/main",
  285. "core/js/myFancyJSFile1",
  286. "core/js/myFancyJSFile4",
  287. "core/js/myFancyJSFile5",
  288. "first/l10n/en",
  289. "first/js/myFirstJSFile",
  290. "files/l10n/en",
  291. "files/js/myFancyJSFile2",
  292. "myApp/l10n/en",
  293. "myApp/js/myFancyJSFile3",
  294. "myApp2/l10n/en",
  295. "myApp2/js/myApp2JSFile",
  296. "myApp5/l10n/en",
  297. "myApp5/js/myApp5JSFile",
  298. "myApp3/l10n/en",
  299. "myApp3/js/myApp3JSFile",
  300. "myApp4/l10n/en",
  301. "myApp4/js/myApp4JSFile",
  302. ];
  303. foreach ($scripts as $script) {
  304. $this->assertContains($script, $scripts);
  305. }
  306. }
  307. public function testAddScriptCircularDependency() {
  308. \OCP\Util::addScript('circular', 'file1', 'dependency');
  309. \OCP\Util::addScript('dependency', 'file2', 'circular');
  310. $scripts = \OCP\Util::getScripts();
  311. $this->assertContains('circular/js/file1', $scripts);
  312. $this->assertContains('dependency/js/file2', $scripts);
  313. }
  314. public function testAddVendorScript() {
  315. \OC_Util::addVendorScript('core', 'myFancyJSFile1');
  316. \OC_Util::addVendorScript('myApp', 'myFancyJSFile2');
  317. \OC_Util::addVendorScript('core', 'myFancyJSFile0', true);
  318. \OC_Util::addVendorScript('core', 'myFancyJSFile10', true);
  319. // add duplicate
  320. \OC_Util::addVendorScript('core', 'myFancyJSFile1');
  321. $this->assertEquals([
  322. 'core/vendor/myFancyJSFile10',
  323. 'core/vendor/myFancyJSFile0',
  324. 'core/vendor/myFancyJSFile1',
  325. 'myApp/vendor/myFancyJSFile2',
  326. ], \OC_Util::$scripts);
  327. $this->assertEquals([], \OC_Util::$styles);
  328. }
  329. public function testAddTranslations() {
  330. \OC_Util::addTranslations('appId', 'de');
  331. $this->assertEquals([
  332. 'appId/l10n/de'
  333. ], \OC_Util::$scripts);
  334. $this->assertEquals([], \OC_Util::$styles);
  335. }
  336. public function testAddStyle() {
  337. \OC_Util::addStyle('core', 'myFancyCSSFile1');
  338. \OC_Util::addStyle('myApp', 'myFancyCSSFile2');
  339. \OC_Util::addStyle('core', 'myFancyCSSFile0', true);
  340. \OC_Util::addStyle('core', 'myFancyCSSFile10', true);
  341. // add duplicate
  342. \OC_Util::addStyle('core', 'myFancyCSSFile1');
  343. $this->assertEquals([], \OC_Util::$scripts);
  344. $this->assertEquals([
  345. 'core/css/myFancyCSSFile10',
  346. 'core/css/myFancyCSSFile0',
  347. 'core/css/myFancyCSSFile1',
  348. 'myApp/css/myFancyCSSFile2',
  349. ], \OC_Util::$styles);
  350. }
  351. public function testAddVendorStyle() {
  352. \OC_Util::addVendorStyle('core', 'myFancyCSSFile1');
  353. \OC_Util::addVendorStyle('myApp', 'myFancyCSSFile2');
  354. \OC_Util::addVendorStyle('core', 'myFancyCSSFile0', true);
  355. \OC_Util::addVendorStyle('core', 'myFancyCSSFile10', true);
  356. // add duplicate
  357. \OC_Util::addVendorStyle('core', 'myFancyCSSFile1');
  358. $this->assertEquals([], \OC_Util::$scripts);
  359. $this->assertEquals([
  360. 'core/vendor/myFancyCSSFile10',
  361. 'core/vendor/myFancyCSSFile0',
  362. 'core/vendor/myFancyCSSFile1',
  363. 'myApp/vendor/myFancyCSSFile2',
  364. ], \OC_Util::$styles);
  365. }
  366. public function testShortenMultibyteString() {
  367. $this->assertEquals('Short nuff', \OCP\Util::shortenMultibyteString('Short nuff', 255));
  368. $this->assertEquals('ABC', \OCP\Util::shortenMultibyteString('ABCDEF', 3));
  369. // each of the characters is 12 bytes
  370. $this->assertEquals('🙈', \OCP\Util::shortenMultibyteString('🙈🙊🙉', 16, 2));
  371. }
  372. }