UtilTest.php 14 KB

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