1<?php
2
3namespace RainLoop;
4
5class Utils
6{
7	/**
8	 * @var string
9	 */
10	static $CookieDefaultPath = '';
11
12	/**
13	 * @var bool|null
14	 */
15	static $CookieDefaultSecure = null;
16
17	static $Cookies = null;
18
19	static $RsaKey = null;
20
21	/**
22	 * @return void
23	 */
24	private function __construct()
25	{
26	}
27
28	/**
29	 * @param string $sFileName
30	 * @param string $sSignature
31	 *
32	 * @return bool
33	 */
34	static public function PgpVerifyFile($sFileName, $sSignature)
35	{
36		$sKeyFile = APP_VERSION_ROOT_PATH.'app/resources/RainLoop.asc';
37		if (\file_exists($sKeyFile) && \file_exists($sFileName) && !empty($sSignature))
38		{
39			$sKeyFile = @\file_get_contents($sKeyFile);
40			return !empty($sKeyFile); // TODO
41		}
42
43		return false;
44	}
45
46	/**
47	 * @return string
48	 */
49	static public function RsaPrivateKey()
50	{
51		if (!empty(\RainLoop\Utils::$RsaKey))
52		{
53			return \RainLoop\Utils::$RsaKey;
54		}
55
56		\RainLoop\Utils::$RsaKey = \file_exists(APP_PRIVATE_DATA.'rsa/private') ?
57			\file_get_contents(APP_PRIVATE_DATA.'rsa/private') : '';
58
59		\RainLoop\Utils::$RsaKey = \is_string(\RainLoop\Utils::$RsaKey) ? \RainLoop\Utils::$RsaKey : '';
60	}
61
62	/**
63	 * @param string $sString
64	 * @param string $sKey = ''
65	 *
66	 * @return string|false
67	 */
68	static public function EncryptStringRSA($sString, $sKey = '')
69	{
70		$sResult = '';
71		$sKey = \md5($sKey);
72
73		$sPrivateKey = \RainLoop\Utils::RsaPrivateKey();
74		if (!empty($sPrivateKey))
75		{
76			$oPrivKey  = \openssl_pkey_get_private($sPrivateKey);
77			$oKeyDetails = \openssl_pkey_get_details($oPrivKey);
78
79			if (!empty($oKeyDetails['key']) && !empty($oKeyDetails['bits']))
80			{
81				$oPubKey = \openssl_pkey_get_public($oKeyDetails['key']);
82
83				$iC = (($oKeyDetails['bits'] / 8) - 15);
84				$aString = \str_split($sString, $iC);
85
86				foreach ($aString as $iIndex => $sLine)
87				{
88					$sEncrypted = '';
89					\openssl_public_encrypt($sLine, $sEncrypted, $oPubKey);
90					$aString[$iIndex] = $sEncrypted;
91				}
92
93				$aString[] = $sKey;
94				$sResult = @\serialize($aString);
95
96				\openssl_free_key($oPubKey);
97			}
98
99			\openssl_free_key($oPrivKey);
100		}
101
102		return $sResult;
103	}
104
105	/**
106	 * @param string $sString
107	 * @param string $sKey = ''
108	 *
109	 * @return string|false
110	 */
111	static public function DecryptStringRSA($sString, $sKey = '')
112	{
113		$sResult = '';
114		$sKey = \md5($sKey);
115
116		$sPrivateKey = \RainLoop\Utils::RsaPrivateKey();
117		if (!empty($sPrivateKey) && !empty($sString))
118		{
119			$oPrivKey  = \openssl_pkey_get_private($sPrivateKey);
120
121			$aString = @\unserialize($sString);
122			if (\is_array($aString))
123			{
124				if ($sKey === \array_pop($aString))
125				{
126					foreach ($aString as $iIndex => $sLine)
127					{
128						$sDecrypted = '';
129						\openssl_private_decrypt($sLine, $sDecrypted, $oPrivKey);
130						$aString[$iIndex] = $sDecrypted;
131					}
132
133					$sResult = \implode('', $aString);
134				}
135			}
136
137			\openssl_free_key($oPrivKey);
138		}
139
140		return $sResult;
141	}
142
143	/**
144	 * @param string $sString
145	 * @param string $sKey
146	 *
147	 * @return string
148	 */
149	static public function EncryptString($sString, $sKey)
150	{
151		return \MailSo\Base\Crypt::XxteaEncrypt($sString, $sKey);
152	}
153
154	/**
155	 * @param string $sEncriptedString
156	 * @param string $sKey
157	 *
158	 * @return string
159	 */
160	static public function DecryptString($sEncriptedString, $sKey)
161	{
162		return \MailSo\Base\Crypt::XxteaDecrypt($sEncriptedString, $sKey);
163	}
164
165	/**
166	 * @param string $sString
167	 * @param string $sKey
168	 *
169	 * @return string
170	 */
171	static public function EncryptStringQ($sString, $sKey)
172	{
173//		if (\MailSo\Base\Utils::FunctionExistsAndEnabled('openssl_pkey_get_private'))
174//		{
175//			return \RainLoop\Utils::EncryptStringRSA($sString,
176//				$sKey.'Q'.\RainLoop\Utils::GetShortToken());
177//		}
178
179		return \MailSo\Base\Crypt::XxteaEncrypt($sString,
180			$sKey.'Q'.\RainLoop\Utils::GetShortToken());
181	}
182
183	/**
184	 * @param string $sEncriptedString
185	 * @param string $sKey
186	 *
187	 * @return string
188	 */
189	static public function DecryptStringQ($sEncriptedString, $sKey)
190	{
191//		if (\MailSo\Base\Utils::FunctionExistsAndEnabled('openssl_pkey_get_private'))
192//		{
193//			return \RainLoop\Utils::DecryptStringRSA($sEncriptedString,
194//				$sKey.'Q'.\RainLoop\Utils::GetShortToken());
195//		}
196
197		return \MailSo\Base\Crypt::XxteaDecrypt($sEncriptedString,
198			$sKey.'Q'.\RainLoop\Utils::GetShortToken());
199	}
200
201	/**
202	 * @param array $aValues
203	 * @param string $sCustomKey = ''
204	 *
205	 * @return string
206	 */
207	static public function EncodeKeyValues(array $aValues, $sCustomKey = '')
208	{
209		return \MailSo\Base\Utils::UrlSafeBase64Encode(
210			\RainLoop\Utils::EncryptString(@\serialize($aValues), \md5(APP_SALT.$sCustomKey)));
211	}
212
213	/**
214	 * @param string $sEncodedValues
215	 * @param string $sCustomKey = ''
216	 *
217	 * @return array
218	 */
219	static public function DecodeKeyValues($sEncodedValues, $sCustomKey = '')
220	{
221		$aResult = @\unserialize(
222			\RainLoop\Utils::DecryptString(
223				\MailSo\Base\Utils::UrlSafeBase64Decode($sEncodedValues), \md5(APP_SALT.$sCustomKey)));
224
225		return \is_array($aResult) ? $aResult : array();
226	}
227
228	/**
229	 * @param array $aValues
230	 * @param string $sCustomKey = ''
231	 *
232	 * @return string
233	 */
234	static public function EncodeKeyValuesQ(array $aValues, $sCustomKey = '')
235	{
236		return \MailSo\Base\Utils::UrlSafeBase64Encode(
237			\RainLoop\Utils::EncryptStringQ(
238				@\serialize($aValues), \md5(APP_SALT.$sCustomKey)));
239	}
240
241	/**
242	 * @param string $sEncodedValues
243	 * @param string $sCustomKey = ''
244	 *
245	 * @return array
246	 */
247	static public function DecodeKeyValuesQ($sEncodedValues, $sCustomKey = '')
248	{
249		$aResult = @\unserialize(
250			\RainLoop\Utils::DecryptStringQ(
251				\MailSo\Base\Utils::UrlSafeBase64Decode($sEncodedValues), \md5(APP_SALT.$sCustomKey)));
252
253		return \is_array($aResult) ? $aResult : array();
254	}
255
256	/**
257	 * @return string
258	 */
259	static public function GetConnectionToken()
260	{
261		$sKey = 'rltoken';
262
263		$sToken = \RainLoop\Utils::GetCookie($sKey, null);
264		if (null === $sToken)
265		{
266			$sToken = \MailSo\Base\Utils::Md5Rand(APP_SALT);
267			\RainLoop\Utils::SetCookie($sKey, $sToken, \time() + 60 * 60 * 24 * 30);
268		}
269
270		return \md5('Connection'.APP_SALT.$sToken.'Token'.APP_SALT);
271	}
272
273	/**
274	 * @return string
275	 */
276	static public function Fingerprint()
277	{
278		return \md5(empty($_SERVER['HTTP_USER_AGENT']) ? 'RainLoopFingerprint' : $_SERVER['HTTP_USER_AGENT']);
279	}
280
281	/**
282	 * @return string
283	 */
284	static public function GetShortToken()
285	{
286		$sKey = 'rlsession';
287
288		$sToken = \RainLoop\Utils::GetCookie($sKey, null);
289		if (null === $sToken)
290		{
291			$sToken = \MailSo\Base\Utils::Md5Rand(APP_SALT);
292			\RainLoop\Utils::SetCookie($sKey, $sToken, 0);
293		}
294
295		return \md5('Session'.APP_SALT.$sToken.'Token'.APP_SALT);
296	}
297
298	/**
299	 * @return void
300	 */
301	static public function UpdateConnectionToken()
302	{
303		$sKey = 'rltoken';
304
305		$sToken = \RainLoop\Utils::GetCookie($sKey, '');
306		if (!empty($sToken))
307		{
308			\RainLoop\Utils::SetCookie($sKey, $sToken, \time() + 60 * 60 * 24 * 30);
309		}
310	}
311
312	/**
313	 * @return string
314	 */
315	static public function GetCsrfToken()
316	{
317		return \md5('Csrf'.APP_SALT.self::GetConnectionToken().'Token'.APP_SALT);
318	}
319
320	/**
321	 * @param string $sPath
322	 *
323	 * @return string
324	 */
325	public static function PathMD5($sPath)
326	{
327		$sResult = '';
328		if (@\is_dir($sPath))
329		{
330			$oDirIterator = new \RecursiveDirectoryIterator($sPath);
331			$oIterator = new \RecursiveIteratorIterator($oDirIterator, \RecursiveIteratorIterator::SELF_FIRST);
332
333			foreach ($oIterator as $oFile)
334			{
335				$sResult = \md5($sResult.($oFile->isFile() ? \md5_file($oFile) : $oFile));
336			}
337		}
338
339		return $sResult;
340	}
341
342	/**
343	 * @param string $sFileName
344	 * @param array $aResultLang
345	 *
346	 * @return void
347	 */
348	public static function ReadAndAddLang($sFileName, &$aResultLang)
349	{
350		if (\file_exists($sFileName))
351		{
352			$isYml = '.yml' === substr($sFileName, -4);
353			if ($isYml)
354			{
355				$aLang = \spyc_load(\str_replace(array(': >-', ': |-', ': |+'), array(': >', ': |', ': |'), \file_get_contents($sFileName)));
356				if (\is_array($aLang))
357				{
358					\reset($aLang);
359					$sLangKey = key($aLang);
360					if (isset($aLang[$sLangKey]) && is_array($aLang[$sLangKey]))
361					{
362						$aLang = $aLang[$sLangKey];
363					}
364					else
365					{
366						$aLang = null;
367					}
368				}
369			}
370			else
371			{
372				$aLang = \RainLoop\Utils::CustomParseIniFile($sFileName, true);
373			}
374
375			if (\is_array($aLang))
376			{
377				foreach ($aLang as $sKey => $mValue)
378				{
379					if (\is_array($mValue))
380					{
381						foreach ($mValue as $sSecKey => $mSecValue)
382						{
383							$aResultLang[$sKey.'/'.$sSecKey] = $mSecValue;
384						}
385					}
386					else
387					{
388						$aResultLang[$sKey] = $mValue;
389					}
390				}
391			}
392		}
393	}
394
395	/**
396	 * @param string $sDir
397	 * @param string $sType = ''
398	 * @return array
399	 */
400	public static function FolderFiles($sDir, $sType = '')
401	{
402		$aResult = array();
403		if (@\is_dir($sDir))
404		{
405			if (false !== ($rDirHandle = @\opendir($sDir)))
406			{
407				while (false !== ($sFile = @\readdir($rDirHandle)))
408				{
409					if (empty($sType) || $sType === \substr($sFile, -\strlen($sType)))
410					{
411						if (\is_file($sDir.'/'.$sFile))
412						{
413							$aResult[] = $sFile;
414						}
415					}
416				}
417
418				@\closedir($rDirHandle);
419			}
420		}
421
422		return $aResult;
423	}
424
425	/**
426	 * @param string $sHtml
427	 *
428	 * @return string
429	 */
430	public static function ClearHtmlOutput($sHtml)
431	{
432//		return $sHtml;
433		return \trim(\str_replace('> <', '><',
434			\str_replace('" />', '"/>',
435			\preg_replace('/[\s]+&nbsp;/i', '&nbsp;',
436			\preg_replace('/&nbsp;[\s]+/i', '&nbsp;',
437			\preg_replace('/[\r\n\t]+/', ' ',
438			$sHtml
439		))))));
440	}
441
442	/**
443	 * @param string $sKey
444	 * @return bool
445	 */
446	public static function FastCheck($sKey)
447	{
448		$bResult = false;
449
450		$aMatches = array();
451		if (!empty($sKey) && 0 < \strlen($sKey) && \preg_match('/^(RL[\d]+)\-(.+)\-([^\-]+)$/', $sKey, $aMatches) && 3 === \count($aMatches))
452		{
453			$bResult = $aMatches[3] === \strtoupper(\base_convert(\crc32(\md5(
454				$aMatches[1].'-'.$aMatches[2].'-')), 10, 32));
455		}
456
457		return $bResult;
458	}
459
460	/**
461	 * @param array $aList
462	 * @param string $sDirName
463	 * @param string $sNameSuffix = ''
464	 */
465	public static function CompileTemplates(&$aList, $sDirName, $sNameSuffix = '')
466	{
467		if (\file_exists($sDirName))
468		{
469			$aFileList = \RainLoop\Utils::FolderFiles($sDirName, '.html');
470
471			foreach ($aFileList as $sName)
472			{
473				$sTemplateName = \substr($sName, 0, -5).$sNameSuffix;
474				$aList[$sTemplateName] = $sDirName.'/'.$sName;
475			}
476		}
477	}
478
479	/**
480	 * @param string $sName
481	 * @param mixed $mDefault = null
482	 * @return mixed
483	 */
484	public static function GetCookie($sName, $mDefault = null)
485	{
486		if (null === \RainLoop\Utils::$Cookies)
487		{
488			\RainLoop\Utils::$Cookies = is_array($_COOKIE) ? $_COOKIE : array();
489		}
490
491		return isset(\RainLoop\Utils::$Cookies[$sName]) ? \RainLoop\Utils::$Cookies[$sName] : $mDefault;
492	}
493
494	public static function SetCookie($sName, $sValue = '', $iExpire = 0, $sPath = null, $sDomain = null, $bSecure = null, $bHttpOnly = true)
495	{
496		if (null === \RainLoop\Utils::$Cookies)
497		{
498			\RainLoop\Utils::$Cookies = is_array($_COOKIE) ? $_COOKIE : array();
499		}
500
501		if (null === $sPath)
502		{
503			$sPath = \RainLoop\Utils::$CookieDefaultPath;
504			$sPath = $sPath && 0 < \strlen($sPath) ? $sPath : null;
505		}
506
507		if (null === $bSecure)
508		{
509			$bSecure = \RainLoop\Utils::$CookieDefaultSecure;
510		}
511
512		\RainLoop\Utils::$Cookies[$sName] = $sValue;
513		@\setcookie($sName, $sValue, $iExpire, $sPath, $sDomain, $bSecure, $bHttpOnly);
514	}
515
516	public static function ClearCookie($sName)
517	{
518		if (null === \RainLoop\Utils::$Cookies)
519		{
520			\RainLoop\Utils::$Cookies = is_array($_COOKIE) ? $_COOKIE : array();
521		}
522
523		$sPath = \RainLoop\Utils::$CookieDefaultPath;
524		$sPath = $sPath && 0 < \strlen($sPath) ? $sPath : null;
525
526		unset(\RainLoop\Utils::$Cookies[$sName]);
527		@\setcookie($sName, '', \time() - 3600 * 24 * 30, $sPath);
528	}
529
530	/**
531	 * @return bool
532	 */
533	public static function IsOwnCloud()
534	{
535		return isset($_ENV['RAINLOOP_OWNCLOUD']) && $_ENV['RAINLOOP_OWNCLOUD'] &&
536			\class_exists('OC');
537	}
538	/**
539	 * @return bool
540	 */
541	public static function IsOwnCloudLoggedIn()
542	{
543		return self::IsOwnCloud() && \class_exists('OCP\User') && \OCP\User::isLoggedIn();
544	}
545
546	/**
547	 * @param string $sV
548	 * @param bool $bEncode = false
549	 *
550	 * @return string
551	 */
552	public static function UrlEncode($sV, $bEncode = false)
553	{
554		return $bEncode ? \urlencode($sV) : $sV;
555	}
556
557	/**
558	 * @return string
559	 */
560	public static function WebPath()
561	{
562		$sAppPath = '';
563		if (\RainLoop\Utils::IsOwnCloud())
564		{
565			if (\class_exists('OC_App'))
566			{
567				$sAppPath = \rtrim(\trim(\OC_App::getAppWebPath('rainloop')), '\\/').'/app/';
568			}
569
570			if (empty($sAppPath))
571			{
572				$sUrl = \MailSo\Base\Http::SingletonInstance()->GetUrl();
573				if ($sUrl && \preg_match('/\/index\.php\/apps\/rainloop/', $sUrl))
574				{
575					$sAppPath = \preg_replace('/\/index\.php\/apps\/rainloop.+$/',
576						'/apps/rainloop/app/', $sUrl);
577				}
578			}
579		}
580
581		return $sAppPath;
582	}
583	/**
584	 * @return string
585	 */
586	public static function WebVersionPath()
587	{
588		return self::WebPath().'rainloop/v/'.APP_VERSION.'/';
589	}
590
591	/**
592	 * @return string
593	 */
594	public static function WebStaticPath()
595	{
596		return self::WebVersionPath().'static/';
597	}
598
599	/**
600	 * @param array $aSuggestions
601	 *
602	 * @return array
603	 */
604	public static function RemoveSuggestionDuplicates($aSuggestions)
605	{
606		$aResult = array();
607
608		if (is_array($aSuggestions))
609		{
610			$aCache = array();
611			foreach ($aSuggestions as $aItem)
612			{
613				$sLine = \implode('~~', $aItem);
614				if (!isset($aCache[$sLine]))
615				{
616					$aCache[$sLine] = true;
617					$aResult[] = $aItem;
618				}
619			}
620		}
621
622		return $aResult;
623	}
624
625	/**
626	 * @param string $sFileName
627	 * @param bool $bProcessSections = false
628	 *
629	 * @return array
630	 */
631	public static function CustomParseIniFile($sFileName, $bProcessSections = false)
632	{
633//		if (\MailSo\Base\Utils::FunctionExistsAndEnabled('parse_ini_file'))
634//		{
635//			return @\parse_ini_file($sFileName, !!$bProcessSections);
636//		}
637
638		$sData = @\file_get_contents($sFileName);
639		return \is_string($sData) ? @\parse_ini_string($sData, !!$bProcessSections) : null;
640	}
641
642	public static function CustomBaseConvert($sNumberInput, $sFromBaseInput = '0123456789', $sToBaseInput = '0123456789')
643	{
644		if ($sFromBaseInput === $sToBaseInput)
645		{
646			return $sNumberInput;
647		}
648
649		$mFromBase = \str_split($sFromBaseInput, 1);
650		$mToBase = \str_split($sToBaseInput, 1);
651		$aNumber = \str_split($sNumberInput, 1);
652		$iFromLen = \strlen($sFromBaseInput);
653		$iToLen = \strlen($sToBaseInput);
654		$numberLen = \strlen($sNumberInput);
655		$mRetVal = '';
656
657		if ($sToBaseInput === '0123456789')
658		{
659			$mRetVal = 0;
660			for ($iIndex = 1; $iIndex <= $numberLen; $iIndex++)
661			{
662				$mRetVal = \bcadd($mRetVal, \bcmul(\array_search($aNumber[$iIndex - 1], $mFromBase), \bcpow($iFromLen, $numberLen - $iIndex)));
663			}
664
665			return $mRetVal;
666		}
667
668		if ($sFromBaseInput != '0123456789')
669		{
670			$sBase10 = \RainLoop\Utils::CustomBaseConvert($sNumberInput, $sFromBaseInput, '0123456789');
671		}
672		else
673		{
674			$sBase10 = $sNumberInput;
675		}
676
677		if ($sBase10 < \strlen($sToBaseInput))
678		{
679			return $mToBase[$sBase10];
680		}
681
682		while ($sBase10 !== '0')
683		{
684			$mRetVal = $mToBase[\bcmod($sBase10, $iToLen)].$mRetVal;
685			$sBase10 = \bcdiv($sBase10, $iToLen, 0);
686		}
687
688		return $mRetVal;
689	}
690}