Common.php 53 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481
  1. <?php
  2. namespace {
  3. use Typecho\I18n;
  4. /**
  5. * @deprecated use json_encode and json_decode directly
  6. */
  7. class Json
  8. {
  9. /**
  10. * @param $value
  11. * @return string
  12. */
  13. public static function encode($value): string
  14. {
  15. return json_encode($value);
  16. }
  17. /**
  18. * @param string $string
  19. * @param bool $assoc
  20. * @return mixed
  21. */
  22. public static function decode(string $string, bool $assoc = false)
  23. {
  24. return json_decode($string, $assoc);
  25. }
  26. }
  27. /**
  28. * I18n function
  29. *
  30. * @param string $string 需要翻译的文字
  31. * @param mixed ...$args 参数
  32. *
  33. * @return string
  34. */
  35. function _t(string $string, ...$args): string
  36. {
  37. if (empty($args)) {
  38. return I18n::translate($string);
  39. } else {
  40. return vsprintf(I18n::translate($string), $args);
  41. }
  42. }
  43. /**
  44. * I18n function, translate and echo
  45. *
  46. * @param string $string 需要翻译的文字
  47. * @param mixed ...$args 参数
  48. */
  49. function _e(string $string, ...$args)
  50. {
  51. array_unshift($args, $string);
  52. echo call_user_func_array('_t', $args);
  53. }
  54. /**
  55. * 针对复数形式的翻译函数
  56. *
  57. * @param string $single 单数形式的翻译
  58. * @param string $plural 复数形式的翻译
  59. * @param integer $number 数字
  60. *
  61. * @return string
  62. */
  63. function _n(string $single, string $plural, int $number): string
  64. {
  65. return str_replace('%d', $number, I18n::ngettext($single, $plural, $number));
  66. }
  67. }
  68. namespace Typecho {
  69. const PLUGIN_NAMESPACE = 'TypechoPlugin';
  70. spl_autoload_register(function (string $className) {
  71. $isDefinedAlias = defined('__TYPECHO_CLASS_ALIASES__');
  72. $isNamespace = strpos($className, '\\') !== false;
  73. $isAlias = $isDefinedAlias && isset(__TYPECHO_CLASS_ALIASES__[$className]);
  74. $isPlugin = false;
  75. // detect if class is predefined
  76. if ($isNamespace) {
  77. $isPlugin = strpos(ltrim($className, '\\'), PLUGIN_NAMESPACE . '\\') !== false;
  78. if ($isPlugin) {
  79. $realClassName = substr($className, strlen(PLUGIN_NAMESPACE) + 1);
  80. $alias = Common::nativeClassName($realClassName);
  81. $path = str_replace('\\', '/', $realClassName);
  82. } else {
  83. if ($isDefinedAlias) {
  84. $alias = array_search('\\' . ltrim($className, '\\'), __TYPECHO_CLASS_ALIASES__);
  85. }
  86. $alias = empty($alias) ? Common::nativeClassName($className) : $alias;
  87. $path = str_replace('\\', '/', $className);
  88. }
  89. } elseif (strpos($className, '_') !== false || $isAlias) {
  90. $isPlugin = !$isAlias && !preg_match("/^(Typecho|Widget|IXR)_/", $className);
  91. if ($isPlugin) {
  92. $alias = '\\TypechoPlugin\\' . str_replace('_', '\\', $className);
  93. $path = str_replace('_', '/', $className);
  94. } else {
  95. $alias = $isAlias ? __TYPECHO_CLASS_ALIASES__[$className]
  96. : '\\' . str_replace('_', '\\', $className);
  97. $path = str_replace('\\', '/', $alias);
  98. }
  99. } else {
  100. $path = $className;
  101. }
  102. if (
  103. isset($alias)
  104. && (class_exists($alias, false)
  105. || interface_exists($alias, false)
  106. || trait_exists($alias, false))
  107. ) {
  108. class_alias($alias, $className, false);
  109. return;
  110. }
  111. // load class file
  112. $path .= '.php';
  113. $defaultFile = __TYPECHO_ROOT_DIR__ . '/var/' . $path;
  114. if (file_exists($defaultFile) && !$isPlugin) {
  115. include_once $defaultFile;
  116. } else {
  117. $pluginFile = __TYPECHO_ROOT_DIR__ . __TYPECHO_PLUGIN_DIR__ . '/' . $path;
  118. if (file_exists($pluginFile)) {
  119. include_once $pluginFile;
  120. } else {
  121. return;
  122. }
  123. }
  124. if (isset($alias)) {
  125. $classLoaded = class_exists($className, false)
  126. || interface_exists($className, false)
  127. || trait_exists($className, false);
  128. $aliasLoaded = class_exists($alias, false)
  129. || interface_exists($alias, false)
  130. || trait_exists($alias, false);
  131. if ($classLoaded && !$aliasLoaded) {
  132. class_alias($className, $alias);
  133. } elseif ($aliasLoaded && !$classLoaded) {
  134. class_alias($alias, $className);
  135. }
  136. }
  137. });
  138. /**
  139. * Typecho公用方法
  140. *
  141. * @category typecho
  142. * @package Common
  143. * @copyright Copyright (c) 2008 Typecho team (http://www.typecho.org)
  144. * @license GNU General Public License 2.0
  145. */
  146. class Common
  147. {
  148. /** 程序版本 */
  149. public const VERSION = '1.2.1';
  150. /**
  151. * 将路径转化为链接
  152. *
  153. * @access public
  154. *
  155. * @param string|null $path 路径
  156. * @param string|null $prefix 前缀
  157. *
  158. * @return string
  159. */
  160. public static function url(?string $path, ?string $prefix): string
  161. {
  162. $path = $path ?? '';
  163. $path = (0 === strpos($path, './')) ? substr($path, 2) : $path;
  164. return rtrim($prefix ?? '', '/') . '/'
  165. . str_replace('//', '/', ltrim($path, '/'));
  166. }
  167. /**
  168. * 程序初始化方法
  169. *
  170. * @access public
  171. * @return void
  172. */
  173. public static function init()
  174. {
  175. // init response
  176. Response::getInstance()->enableAutoSendHeaders(false);
  177. ob_start(function ($content) {
  178. Response::getInstance()->sendHeaders();
  179. return $content;
  180. });
  181. /** 设置异常截获函数 */
  182. set_exception_handler(function (\Throwable $exception) {
  183. echo '<pre><code>';
  184. echo '<h1>' . htmlspecialchars($exception->getMessage()) . '</h1>';
  185. echo htmlspecialchars($exception->__toString());
  186. echo '</code></pre>';
  187. exit;
  188. });
  189. }
  190. /**
  191. * 输出错误页面
  192. *
  193. * @param \Throwable $exception 错误信息
  194. */
  195. public static function error(\Throwable $exception)
  196. {
  197. $code = $exception->getCode() ?: 500;
  198. $message = $exception->getMessage();
  199. if ($exception instanceof \Typecho\Db\Exception) {
  200. $code = 500;
  201. //覆盖原始错误信息
  202. $message = 'Database Server Error';
  203. if ($exception instanceof \Typecho\Db\Adapter\ConnectionException) {
  204. $code = 503;
  205. $message = 'Error establishing a database connection';
  206. } elseif ($exception instanceof \Typecho\Db\Adapter\SQLException) {
  207. $message = 'Database Query Error';
  208. }
  209. }
  210. /** 设置http code */
  211. if (is_numeric($code) && $code > 200) {
  212. Response::getInstance()->setStatus($code);
  213. }
  214. $message = nl2br($message);
  215. if (defined('__TYPECHO_EXCEPTION_FILE__')) {
  216. require_once __TYPECHO_EXCEPTION_FILE__;
  217. } else {
  218. echo
  219. <<<EOF
  220. <!DOCTYPE html>
  221. <html lang="en">
  222. <head>
  223. <meta charset="UTF-8">
  224. <title>{$code}</title>
  225. <style>
  226. html {
  227. padding: 50px 10px;
  228. font-size: 16px;
  229. line-height: 1.4;
  230. color: #666;
  231. background: #F6F6F3;
  232. -webkit-text-size-adjust: 100%;
  233. -ms-text-size-adjust: 100%;
  234. }
  235. html,
  236. input { font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; }
  237. body {
  238. max-width: 500px;
  239. _width: 500px;
  240. padding: 30px 20px;
  241. margin: 0 auto;
  242. background: #FFF;
  243. }
  244. ul {
  245. padding: 0 0 0 40px;
  246. }
  247. .container {
  248. max-width: 380px;
  249. _width: 380px;
  250. margin: 0 auto;
  251. }
  252. </style>
  253. </head>
  254. <body>
  255. <div class="container">
  256. {$message}
  257. </div>
  258. </body>
  259. </html>
  260. EOF;
  261. }
  262. exit(1);
  263. }
  264. /**
  265. * @param string $className 类名
  266. * @return boolean
  267. * @deprecated
  268. */
  269. public static function isAvailableClass(string $className): bool
  270. {
  271. return class_exists($className);
  272. }
  273. /**
  274. * @param array $value
  275. * @param $key
  276. *
  277. * @return array
  278. * @deprecated use array_column instead
  279. *
  280. */
  281. public static function arrayFlatten(array $value, $key): array
  282. {
  283. return array_column($value, $key);
  284. }
  285. /**
  286. * @param string $className
  287. * @return string
  288. */
  289. public static function nativeClassName(string $className): string
  290. {
  291. return trim(str_replace('\\', '_', $className), '_');
  292. }
  293. /**
  294. * 根据count数目来输出字符
  295. * <code>
  296. * echo splitByCount(20, 10, 20, 30, 40, 50);
  297. * </code>
  298. *
  299. * @param int $count
  300. * @param int ...$sizes
  301. * @return int
  302. */
  303. public static function splitByCount(int $count, int ...$sizes): int
  304. {
  305. foreach ($sizes as $size) {
  306. if ($count < $size) {
  307. return $size;
  308. }
  309. }
  310. return 0;
  311. }
  312. /**
  313. * 自闭合html修复函数
  314. * 使用方法:
  315. * <code>
  316. * $input = '这是一段被截断的html文本<a href="#"';
  317. * echo Common::fixHtml($input);
  318. * //output: 这是一段被截断的html文本
  319. * </code>
  320. *
  321. * @param string|null $string 需要修复处理的字符串
  322. * @return string|null
  323. */
  324. public static function fixHtml(?string $string): ?string
  325. {
  326. //关闭自闭合标签
  327. $startPos = strrpos($string, "<");
  328. if (false == $startPos) {
  329. return $string;
  330. }
  331. $trimString = substr($string, $startPos);
  332. if (false === strpos($trimString, ">")) {
  333. $string = substr($string, 0, $startPos);
  334. }
  335. //非自闭合html标签列表
  336. preg_match_all("/<([_0-9a-zA-Z-\:]+)\s*([^>]*)>/is", $string, $startTags);
  337. preg_match_all("/<\/([_0-9a-zA-Z-\:]+)>/is", $string, $closeTags);
  338. if (!empty($startTags[1]) && is_array($startTags[1])) {
  339. krsort($startTags[1]);
  340. $closeTagsIsArray = is_array($closeTags[1]);
  341. foreach ($startTags[1] as $key => $tag) {
  342. $attrLength = strlen($startTags[2][$key]);
  343. if ($attrLength > 0 && "/" == trim($startTags[2][$key][$attrLength - 1])) {
  344. continue;
  345. }
  346. // 白名单
  347. if (
  348. preg_match(
  349. "/^(area|base|br|col|embed|hr|img|input|keygen|link|meta|param|source|track|wbr)$/i",
  350. $tag
  351. )
  352. ) {
  353. continue;
  354. }
  355. if (!empty($closeTags[1]) && $closeTagsIsArray) {
  356. if (false !== ($index = array_search($tag, $closeTags[1]))) {
  357. unset($closeTags[1][$index]);
  358. continue;
  359. }
  360. }
  361. $string .= "</{$tag}>";
  362. }
  363. }
  364. return preg_replace("/\<br\s*\/\>\s*\<\/p\>/is", '</p>', $string);
  365. }
  366. /**
  367. * 去掉字符串中的html标签
  368. * 使用方法:
  369. * <code>
  370. * $input = '<a href="http://test/test.php" title="example">hello</a>';
  371. * $output = Common::stripTags($input, <a href="">);
  372. * echo $output;
  373. * //display: '<a href="http://test/test.php">hello</a>'
  374. * </code>
  375. *
  376. * @param string|null $html 需要处理的字符串
  377. * @param string|null $allowableTags 需要忽略的html标签
  378. * @return string
  379. */
  380. public static function stripTags(?string $html, ?string $allowableTags = null): string
  381. {
  382. $normalizeTags = '';
  383. $allowableAttributes = [];
  384. if (!empty($allowableTags) && preg_match_all("/\<([_a-z0-9-]+)([^>]*)\>/is", $allowableTags, $tags)) {
  385. $normalizeTags = '<' . implode('><', array_map('strtolower', $tags[1])) . '>';
  386. $attributes = array_map('trim', $tags[2]);
  387. foreach ($attributes as $key => $val) {
  388. $allowableAttributes[strtolower($tags[1][$key])] =
  389. array_map('strtolower', array_keys(self::parseAttrs($val)));
  390. }
  391. }
  392. $html = strip_tags($html, $normalizeTags);
  393. return preg_replace_callback(
  394. "/<([_a-z0-9-]+)(\s+[^>]+)?>/is",
  395. function ($matches) use ($allowableAttributes) {
  396. if (!isset($matches[2])) {
  397. return $matches[0];
  398. }
  399. $str = trim($matches[2]);
  400. if (empty($str)) {
  401. return $matches[0];
  402. }
  403. $attrs = self::parseAttrs($str);
  404. $parsedAttrs = [];
  405. $tag = strtolower($matches[1]);
  406. foreach ($attrs as $key => $val) {
  407. if (in_array($key, $allowableAttributes[$tag])) {
  408. $parsedAttrs[] = " {$key}" . (empty($val) ? '' : "={$val}");
  409. }
  410. }
  411. return '<' . $tag . implode('', $parsedAttrs) . '>';
  412. },
  413. $html
  414. );
  415. }
  416. /**
  417. * 过滤用于搜索的字符串
  418. *
  419. * @access public
  420. *
  421. * @param string|null $query 搜索字符串
  422. *
  423. * @return string
  424. */
  425. public static function filterSearchQuery(?string $query): string
  426. {
  427. return isset($query) ? str_replace('-', ' ', self::slugName($query) ?? '') : '';
  428. }
  429. /**
  430. * 生成缩略名
  431. *
  432. * @access public
  433. *
  434. * @param string|null $str 需要生成缩略名的字符串
  435. * @param string|null $default 默认的缩略名
  436. * @param integer $maxLength 缩略名最大长度
  437. *
  438. * @return string
  439. */
  440. public static function slugName(?string $str, ?string $default = null, int $maxLength = 128): ?string
  441. {
  442. $str = trim($str ?? '');
  443. if (!strlen($str)) {
  444. return $default;
  445. }
  446. mb_regex_encoding('UTF-8');
  447. mb_ereg_search_init($str, "[\w" . preg_quote('_-') . "]+");
  448. $result = mb_ereg_search();
  449. $return = '';
  450. if ($result) {
  451. $regs = mb_ereg_search_getregs();
  452. $pos = 0;
  453. do {
  454. $return .= ($pos > 0 ? '-' : '') . $regs[0];
  455. $pos++;
  456. } while ($regs = mb_ereg_search_regs());
  457. }
  458. $str = trim($return, '-_');
  459. $str = !strlen($str) ? $default : $str;
  460. return substr($str, 0, $maxLength);
  461. }
  462. /**
  463. * 将url中的非法字符串
  464. *
  465. * @param string $url 需要过滤的url
  466. *
  467. * @return string
  468. */
  469. public static function safeUrl($url)
  470. {
  471. //~ 针对location的xss过滤, 因为其特殊性无法使用removeXSS函数
  472. //~ fix issue 66
  473. $params = parse_url(str_replace(["\r", "\n", "\t", ' '], '', $url));
  474. /** 禁止非法的协议跳转 */
  475. if (isset($params['scheme'])) {
  476. if (!in_array($params['scheme'], ['http', 'https'])) {
  477. return '/';
  478. }
  479. }
  480. $params = array_map(function ($string) {
  481. $string = str_replace(['%0d', '%0a'], '', strip_tags($string));
  482. return preg_replace([
  483. "/\(\s*(\"|')/i", //函数开头
  484. "/(\"|')\s*\)/i", //函数结尾
  485. ], '', $string);
  486. }, $params);
  487. return self::buildUrl($params);
  488. }
  489. /**
  490. * 根据parse_url的结果重新组合url
  491. *
  492. * @param array $params 解析后的参数
  493. *
  494. * @return string
  495. */
  496. public static function buildUrl(array $params): string
  497. {
  498. return (isset($params['scheme']) ? $params['scheme'] . '://' : null)
  499. . (isset($params['user']) ? $params['user']
  500. . (isset($params['pass']) ? ':' . $params['pass'] : null) . '@' : null)
  501. . ($params['host'] ?? null)
  502. . (isset($params['port']) ? ':' . $params['port'] : null)
  503. . ($params['path'] ?? null)
  504. . (isset($params['query']) ? '?' . $params['query'] : null)
  505. . (isset($params['fragment']) ? '#' . $params['fragment'] : null);
  506. }
  507. /**
  508. * 处理XSS跨站攻击的过滤函数
  509. *
  510. * @param string|null $val 需要处理的字符串
  511. * @return string
  512. */
  513. public static function removeXSS(?string $val): string
  514. {
  515. // remove all non-printable characters. CR(0a) and LF(0b) and TAB(9) are allowed
  516. // this prevents some character re-spacing such as <java\0script>
  517. // note that you have to handle splits with \n, \r, and \t later since they *are* allowed in some inputs
  518. $val = preg_replace('/([\x00-\x08]|[\x0b-\x0c]|[\x0e-\x19])/', '', $val);
  519. // straight replacements, the user should never need these since they're normal characters
  520. // this prevents like <IMG SRC=&#X40&#X61&#X76&#X61&#X73&#X63&#X72&#X69&#X70&#X74&#X3A&#X61&#X6C&#X65&#X72&#X74&#X28&#X27&#X58&#X53&#X53&#X27&#X29>
  521. $search = 'abcdefghijklmnopqrstuvwxyz';
  522. $search .= 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
  523. $search .= '1234567890!@#$%^&*()';
  524. $search .= '~`";:?+/={}[]-_|\'\\';
  525. for ($i = 0; $i < strlen($search); $i++) {
  526. // ;? matches the ;, which is optional
  527. // 0{0,7} matches any padded zeros, which are optional and go up to 8 chars
  528. // &#x0040 @ search for the hex values
  529. $val = preg_replace('/(&#[xX]0{0,8}' . dechex(ord($search[$i])) . ';?)/i', $search[$i], $val);
  530. // &#00064 @ 0{0,7} matches '0' zero to seven times
  531. $val = preg_replace('/(&#0{0,8}' . ord($search[$i]) . ';?)/', $search[$i], $val); // with a ;
  532. }
  533. // now the only remaining whitespace attacks are \t, \n, and \r
  534. $ra1 = ['javascript', 'vbscript', 'expression', 'applet', 'meta', 'xml', 'blink', 'link', 'style', 'script',
  535. 'embed', 'object', 'iframe', 'frame', 'frameset', 'ilayer', 'layer', 'bgsound', 'title', 'base'];
  536. $ra2 = [
  537. 'onabort', 'onactivate', 'onafterprint', 'onafterupdate', 'onbeforeactivate', 'onbeforecopy',
  538. 'onbeforecut', 'onbeforedeactivate', 'onbeforeeditfocus', 'onbeforepaste', 'onbeforeprint',
  539. 'onbeforeunload', 'onbeforeupdate', 'onblur', 'onbounce', 'oncellchange', 'onchange', 'onclick',
  540. 'oncontextmenu', 'oncontrolselect', 'oncopy', 'oncut', 'ondataavailable', 'ondatasetchanged',
  541. 'ondatasetcomplete', 'ondblclick', 'ondeactivate', 'ondrag', 'ondragend', 'ondragenter', 'ondragleave',
  542. 'ondragover', 'ondragstart', 'ondrop', 'onerror', 'onerrorupdate', 'onfilterchange', 'onfinish',
  543. 'onfocus', 'onfocusin', 'onfocusout', 'onhelp', 'onkeydown', 'onkeypress', 'onkeyup',
  544. 'onlayoutcomplete', 'onload', 'onlosecapture', 'onmousedown', 'onmouseenter',
  545. 'onmouseleave', 'onmousemove', 'onmouseout', 'onmouseover', 'onmouseup', 'onmousewheel',
  546. 'onmove', 'onmoveend', 'onmovestart', 'onpaste', 'onpropertychange', 'onreadystatechange',
  547. 'onreset', 'onresize', 'onresizeend', 'onresizestart', 'onrowenter', 'onrowexit', 'onrowsdelete',
  548. 'onrowsinserted', 'onscroll', 'onselect', 'onselectionchange', 'onselectstart', 'onstart', 'onstop',
  549. 'onsubmit', 'onunload'
  550. ];
  551. $ra = array_merge($ra1, $ra2);
  552. $found = true; // keep replacing as long as the previous round replaced something
  553. while ($found == true) {
  554. $val_before = $val;
  555. for ($i = 0; $i < sizeof($ra); $i++) {
  556. $pattern = '/';
  557. for ($j = 0; $j < strlen($ra[$i]); $j++) {
  558. if ($j > 0) {
  559. $pattern .= '(';
  560. $pattern .= '(&#[xX]0{0,8}([9ab]);)';
  561. $pattern .= '|';
  562. $pattern .= '|(&#0{0,8}([9|10|13]);)';
  563. $pattern .= ')*';
  564. }
  565. $pattern .= $ra[$i][$j];
  566. }
  567. $pattern .= '/i';
  568. $replacement = substr($ra[$i], 0, 2) . '<x>' . substr($ra[$i], 2); // add in <> to nerf the tag
  569. $val = preg_replace($pattern, $replacement, $val); // filter out the hex tags
  570. if ($val_before == $val) {
  571. // no replacements were made, so exit the loop
  572. $found = false;
  573. }
  574. }
  575. }
  576. return $val;
  577. }
  578. /**
  579. * 宽字符串截字函数
  580. *
  581. * @param string $str 需要截取的字符串
  582. * @param integer $start 开始截取的位置
  583. * @param integer $length 需要截取的长度
  584. * @param string $trim 截取后的截断标示符
  585. *
  586. * @return string
  587. */
  588. public static function subStr(string $str, int $start, int $length, string $trim = "..."): string
  589. {
  590. if (!strlen($str)) {
  591. return '';
  592. }
  593. $iLength = self::strLen($str) - $start;
  594. $tLength = $length < $iLength ? ($length - self::strLen($trim)) : $length;
  595. $str = mb_substr($str, $start, $tLength, 'UTF-8');
  596. return $length < $iLength ? ($str . $trim) : $str;
  597. }
  598. /**
  599. * 获取宽字符串长度函数
  600. *
  601. * @param string $str 需要获取长度的字符串
  602. * @return integer
  603. */
  604. public static function strLen(string $str): int
  605. {
  606. return mb_strlen($str, 'UTF-8');
  607. }
  608. /**
  609. * 判断hash值是否相等
  610. *
  611. * @access public
  612. *
  613. * @param string|null $from 源字符串
  614. * @param string|null $to 目标字符串
  615. *
  616. * @return boolean
  617. */
  618. public static function hashValidate(?string $from, ?string $to): bool
  619. {
  620. if (!isset($from) || !isset($to)) {
  621. return false;
  622. }
  623. if ('$T$' == substr($to, 0, 3)) {
  624. $salt = substr($to, 3, 9);
  625. return self::hash($from, $salt) === $to;
  626. } else {
  627. return md5($from) === $to;
  628. }
  629. }
  630. /**
  631. * 对字符串进行hash加密
  632. *
  633. * @access public
  634. *
  635. * @param string|null $string 需要hash的字符串
  636. * @param string|null $salt 扰码
  637. *
  638. * @return string
  639. */
  640. public static function hash(?string $string, ?string $salt = null): string
  641. {
  642. if (!isset($string)) {
  643. return '';
  644. }
  645. /** 生成随机字符串 */
  646. $salt = empty($salt) ? self::randString(9) : $salt;
  647. $length = strlen($string);
  648. if ($length == 0) {
  649. return '';
  650. }
  651. $hash = '';
  652. $last = ord($string[$length - 1]);
  653. $pos = 0;
  654. /** 判断扰码长度 */
  655. if (strlen($salt) != 9) {
  656. /** 如果不是9直接返回 */
  657. return '';
  658. }
  659. while ($pos < $length) {
  660. $asc = ord($string[$pos]);
  661. $last = ($last * ord($salt[($last % $asc) % 9]) + $asc) % 95 + 32;
  662. $hash .= chr($last);
  663. $pos++;
  664. }
  665. return '$T$' . $salt . md5($hash);
  666. }
  667. /**
  668. * 生成随机字符串
  669. *
  670. * @access public
  671. *
  672. * @param integer $length 字符串长度
  673. * @param boolean $specialChars 是否有特殊字符
  674. *
  675. * @return string
  676. */
  677. public static function randString(int $length, bool $specialChars = false): string
  678. {
  679. $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
  680. if ($specialChars) {
  681. $chars .= '!@#$%^&*()';
  682. }
  683. $result = '';
  684. $max = strlen($chars) - 1;
  685. for ($i = 0; $i < $length; $i++) {
  686. $result .= $chars[rand(0, $max)];
  687. }
  688. return $result;
  689. }
  690. /**
  691. * 创建一个会过期的Token
  692. *
  693. * @param $secret
  694. * @return string
  695. */
  696. public static function timeToken($secret): string
  697. {
  698. return sha1($secret . '&' . time());
  699. }
  700. /**
  701. * 在时间范围内验证token
  702. *
  703. * @param $token
  704. * @param $secret
  705. * @param int $timeout
  706. * @return bool
  707. */
  708. public static function timeTokenValidate($token, $secret, int $timeout = 5): bool
  709. {
  710. $now = time();
  711. $from = $now - $timeout;
  712. for ($i = $now; $i >= $from; $i--) {
  713. if (sha1($secret . '&' . $i) == $token) {
  714. return true;
  715. }
  716. }
  717. return false;
  718. }
  719. /**
  720. * 获取gravatar头像地址
  721. *
  722. * @param string|null $mail
  723. * @param int $size
  724. * @param string|null $rating
  725. * @param string|null $default
  726. * @param bool $isSecure
  727. *
  728. * @return string
  729. */
  730. public static function gravatarUrl(
  731. ?string $mail,
  732. int $size,
  733. ?string $rating = null,
  734. ?string $default = null,
  735. bool $isSecure = true
  736. ): string {
  737. if (defined('__TYPECHO_GRAVATAR_PREFIX__')) {
  738. $url = __TYPECHO_GRAVATAR_PREFIX__;
  739. } else {
  740. $url = $isSecure ? 'https://secure.gravatar.com' : 'http://www.gravatar.com';
  741. $url .= '/avatar/';
  742. }
  743. if (!empty($mail)) {
  744. $url .= md5(strtolower(trim($mail)));
  745. }
  746. $url .= '?s=' . $size;
  747. if (isset($rating)) {
  748. $url .= '&amp;r=' . $rating;
  749. }
  750. if (isset($default)) {
  751. $url .= '&amp;d=' . $default;
  752. }
  753. return $url;
  754. }
  755. /**
  756. * 给javascript赋值加入扰码设计
  757. *
  758. * @param string $value
  759. *
  760. * @return string
  761. */
  762. public static function shuffleScriptVar(string $value): string
  763. {
  764. $length = strlen($value);
  765. $max = 3;
  766. $offset = 0;
  767. $result = [];
  768. $cut = [];
  769. while ($length > 0) {
  770. $len = rand(0, min($max, $length));
  771. $rand = "'" . self::randString(rand(1, $max)) . "'";
  772. if ($len > 0) {
  773. $val = "'" . substr($value, $offset, $len) . "'";
  774. $result[] = rand(0, 1) ? "//{$rand}\n{$val}" : "{$val}//{$rand}\n";
  775. } else {
  776. if (rand(0, 1)) {
  777. $result[] = rand(0, 1) ? "''///*{$rand}*/{$rand}\n" : "/* {$rand}//{$rand} */''";
  778. } else {
  779. $result[] = rand(0, 1) ? "//{$rand}\n{$rand}" : "{$rand}//{$rand}\n";
  780. $cut[] = [$offset, strlen($rand) - 2 + $offset];
  781. }
  782. }
  783. $offset += $len;
  784. $length -= $len;
  785. }
  786. $name = '_' . self::randString(rand(3, 7));
  787. $cutName = '_' . self::randString(rand(3, 7));
  788. $var = implode('+', $result);
  789. $cutVar = json_encode($cut);
  790. return "(function () {
  791. var {$name} = {$var}, {$cutName} = {$cutVar};
  792. for (var i = 0; i < {$cutName}.length; i ++) {
  793. {$name} = {$name}.substring(0, {$cutName}[i][0]) + {$name}.substring({$cutName}[i][1]);
  794. }
  795. return {$name};
  796. })();";
  797. }
  798. /**
  799. * 创建备份文件缓冲
  800. *
  801. * @param string $type
  802. * @param string $header
  803. * @param string $body
  804. *
  805. * @return string
  806. */
  807. public static function buildBackupBuffer(string $type, string $header, string $body): string
  808. {
  809. $buffer = '';
  810. $buffer .= pack('vvV', $type, strlen($header), strlen($body));
  811. $buffer .= $header . $body;
  812. $buffer .= md5($buffer);
  813. return $buffer;
  814. }
  815. /**
  816. * 从备份文件中解压
  817. *
  818. * @param resource $fp
  819. * @param int|null $offset
  820. * @param string $version
  821. * @return array|bool
  822. */
  823. public static function extractBackupBuffer($fp, ?int &$offset, string $version)
  824. {
  825. $realMetaLen = $version == 'FILE' ? 6 : 8;
  826. $meta = fread($fp, $realMetaLen);
  827. $offset += $realMetaLen;
  828. $metaLen = strlen($meta);
  829. if (false === $meta || $metaLen != $realMetaLen) {
  830. return false;
  831. }
  832. [$type, $headerLen, $bodyLen]
  833. = array_values(unpack($version == 'FILE' ? 'v3' : 'v1type/v1headerLen/V1bodyLen', $meta));
  834. $header = @fread($fp, $headerLen);
  835. $offset += $headerLen;
  836. if (false === $header || strlen($header) != $headerLen) {
  837. return false;
  838. }
  839. if ('FILE' == $version) {
  840. $bodyLen = array_reduce(json_decode($header, true), function ($carry, $len) {
  841. return null === $len ? $carry : $carry + $len;
  842. }, 0);
  843. }
  844. $body = @fread($fp, $bodyLen);
  845. $offset += $bodyLen;
  846. if (false === $body || strlen($body) != $bodyLen) {
  847. return false;
  848. }
  849. $md5 = @fread($fp, 32);
  850. $offset += 32;
  851. if (false === $md5 || $md5 != md5($meta . $header . $body)) {
  852. return false;
  853. }
  854. return [$type, $header, $body];
  855. }
  856. /**
  857. * 检查是否是一个安全的主机名
  858. *
  859. * @param string $host
  860. * @return bool
  861. */
  862. public static function checkSafeHost(string $host): bool
  863. {
  864. if ('localhost' == $host) {
  865. return false;
  866. }
  867. $address = gethostbyname($host);
  868. $inet = inet_pton($address);
  869. if (false === $inet) {
  870. // 有可能是ipv6的地址
  871. $records = dns_get_record($host, DNS_AAAA);
  872. if (empty($records)) {
  873. return false;
  874. }
  875. $address = $records[0]['ipv6'];
  876. $inet = inet_pton($address);
  877. }
  878. return filter_var(
  879. $address,
  880. FILTER_VALIDATE_IP,
  881. FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE
  882. ) !== false;
  883. }
  884. /**
  885. * @return bool
  886. * @deprecated after 1.2.0
  887. */
  888. public static function isAppEngine(): bool
  889. {
  890. return false;
  891. }
  892. /**
  893. * 获取图片
  894. *
  895. * @access public
  896. *
  897. * @param string $fileName 文件名
  898. *
  899. * @return string
  900. */
  901. public static function mimeContentType(string $fileName): string
  902. {
  903. //改为并列判断
  904. if (function_exists('mime_content_type')) {
  905. return mime_content_type($fileName);
  906. }
  907. if (function_exists('finfo_open')) {
  908. $fInfo = @finfo_open(FILEINFO_MIME_TYPE);
  909. if (false !== $fInfo) {
  910. $mimeType = finfo_file($fInfo, $fileName);
  911. finfo_close($fInfo);
  912. return $mimeType;
  913. }
  914. }
  915. $mimeTypes = [
  916. 'ez' => 'application/andrew-inset',
  917. 'csm' => 'application/cu-seeme',
  918. 'cu' => 'application/cu-seeme',
  919. 'tsp' => 'application/dsptype',
  920. 'spl' => 'application/x-futuresplash',
  921. 'hta' => 'application/hta',
  922. 'cpt' => 'image/x-corelphotopaint',
  923. 'hqx' => 'application/mac-binhex40',
  924. 'nb' => 'application/mathematica',
  925. 'mdb' => 'application/msaccess',
  926. 'doc' => 'application/msword',
  927. 'dot' => 'application/msword',
  928. 'bin' => 'application/octet-stream',
  929. 'oda' => 'application/oda',
  930. 'ogg' => 'application/ogg',
  931. 'oga' => 'audio/ogg',
  932. 'ogv' => 'video/ogg',
  933. 'prf' => 'application/pics-rules',
  934. 'key' => 'application/pgp-keys',
  935. 'pdf' => 'application/pdf',
  936. 'pgp' => 'application/pgp-signature',
  937. 'ps' => 'application/postscript',
  938. 'ai' => 'application/postscript',
  939. 'eps' => 'application/postscript',
  940. 'rss' => 'application/rss+xml',
  941. 'rtf' => 'text/rtf',
  942. 'smi' => 'application/smil',
  943. 'smil' => 'application/smil',
  944. 'wp5' => 'application/wordperfect5.1',
  945. 'xht' => 'application/xhtml+xml',
  946. 'xhtml' => 'application/xhtml+xml',
  947. 'zip' => 'application/zip',
  948. 'cdy' => 'application/vnd.cinderella',
  949. 'mif' => 'application/x-mif',
  950. 'xls' => 'application/vnd.ms-excel',
  951. 'xlb' => 'application/vnd.ms-excel',
  952. 'cat' => 'application/vnd.ms-pki.seccat',
  953. 'stl' => 'application/vnd.ms-pki.stl',
  954. 'ppt' => 'application/vnd.ms-powerpoint',
  955. 'pps' => 'application/vnd.ms-powerpoint',
  956. 'pot' => 'application/vnd.ms-powerpoint',
  957. 'sdc' => 'application/vnd.stardivision.calc',
  958. 'sda' => 'application/vnd.stardivision.draw',
  959. 'sdd' => 'application/vnd.stardivision.impress',
  960. 'sdp' => 'application/vnd.stardivision.impress',
  961. 'smf' => 'application/vnd.stardivision.math',
  962. 'sdw' => 'application/vnd.stardivision.writer',
  963. 'vor' => 'application/vnd.stardivision.writer',
  964. 'sgl' => 'application/vnd.stardivision.writer-global',
  965. 'sxc' => 'application/vnd.sun.xml.calc',
  966. 'stc' => 'application/vnd.sun.xml.calc.template',
  967. 'sxd' => 'application/vnd.sun.xml.draw',
  968. 'std' => 'application/vnd.sun.xml.draw.template',
  969. 'sxi' => 'application/vnd.sun.xml.impress',
  970. 'sti' => 'application/vnd.sun.xml.impress.template',
  971. 'sxm' => 'application/vnd.sun.xml.math',
  972. 'sxw' => 'application/vnd.sun.xml.writer',
  973. 'sxg' => 'application/vnd.sun.xml.writer.global',
  974. 'stw' => 'application/vnd.sun.xml.writer.template',
  975. 'sis' => 'application/vnd.symbian.install',
  976. 'wbxml' => 'application/vnd.wap.wbxml',
  977. 'wmlc' => 'application/vnd.wap.wmlc',
  978. 'wmlsc' => 'application/vnd.wap.wmlscriptc',
  979. 'wk' => 'application/x-123',
  980. 'dmg' => 'application/x-apple-diskimage',
  981. 'bcpio' => 'application/x-bcpio',
  982. 'torrent' => 'application/x-bittorrent',
  983. 'cdf' => 'application/x-cdf',
  984. 'vcd' => 'application/x-cdlink',
  985. 'pgn' => 'application/x-chess-pgn',
  986. 'cpio' => 'application/x-cpio',
  987. 'csh' => 'text/x-csh',
  988. 'deb' => 'application/x-debian-package',
  989. 'dcr' => 'application/x-director',
  990. 'dir' => 'application/x-director',
  991. 'dxr' => 'application/x-director',
  992. 'wad' => 'application/x-doom',
  993. 'dms' => 'application/x-dms',
  994. 'dvi' => 'application/x-dvi',
  995. 'pfa' => 'application/x-font',
  996. 'pfb' => 'application/x-font',
  997. 'gsf' => 'application/x-font',
  998. 'pcf' => 'application/x-font',
  999. 'pcf.Z' => 'application/x-font',
  1000. 'gnumeric' => 'application/x-gnumeric',
  1001. 'sgf' => 'application/x-go-sgf',
  1002. 'gcf' => 'application/x-graphing-calculator',
  1003. 'gtar' => 'application/x-gtar',
  1004. 'tgz' => 'application/x-gtar',
  1005. 'taz' => 'application/x-gtar',
  1006. 'gz' => 'application/x-gtar',
  1007. 'hdf' => 'application/x-hdf',
  1008. 'phtml' => 'application/x-httpd-php',
  1009. 'pht' => 'application/x-httpd-php',
  1010. 'php' => 'application/x-httpd-php',
  1011. 'phps' => 'application/x-httpd-php-source',
  1012. 'php3' => 'application/x-httpd-php3',
  1013. 'php3p' => 'application/x-httpd-php3-preprocessed',
  1014. 'php4' => 'application/x-httpd-php4',
  1015. 'ica' => 'application/x-ica',
  1016. 'ins' => 'application/x-internet-signup',
  1017. 'isp' => 'application/x-internet-signup',
  1018. 'iii' => 'application/x-iphone',
  1019. 'jar' => 'application/x-java-archive',
  1020. 'jnlp' => 'application/x-java-jnlp-file',
  1021. 'ser' => 'application/x-java-serialized-object',
  1022. 'class' => 'application/x-java-vm',
  1023. 'js' => 'application/x-javascript',
  1024. 'chrt' => 'application/x-kchart',
  1025. 'kil' => 'application/x-killustrator',
  1026. 'kpr' => 'application/x-kpresenter',
  1027. 'kpt' => 'application/x-kpresenter',
  1028. 'skp' => 'application/x-koan',
  1029. 'skd' => 'application/x-koan',
  1030. 'skt' => 'application/x-koan',
  1031. 'skm' => 'application/x-koan',
  1032. 'ksp' => 'application/x-kspread',
  1033. 'kwd' => 'application/x-kword',
  1034. 'kwt' => 'application/x-kword',
  1035. 'latex' => 'application/x-latex',
  1036. 'lha' => 'application/x-lha',
  1037. 'lzh' => 'application/x-lzh',
  1038. 'lzx' => 'application/x-lzx',
  1039. 'frm' => 'application/x-maker',
  1040. 'maker' => 'application/x-maker',
  1041. 'frame' => 'application/x-maker',
  1042. 'fm' => 'application/x-maker',
  1043. 'fb' => 'application/x-maker',
  1044. 'book' => 'application/x-maker',
  1045. 'fbdoc' => 'application/x-maker',
  1046. 'wmz' => 'application/x-ms-wmz',
  1047. 'wmd' => 'application/x-ms-wmd',
  1048. 'com' => 'application/x-msdos-program',
  1049. 'exe' => 'application/x-msdos-program',
  1050. 'bat' => 'application/x-msdos-program',
  1051. 'dll' => 'application/x-msdos-program',
  1052. 'msi' => 'application/x-msi',
  1053. 'nc' => 'application/x-netcdf',
  1054. 'pac' => 'application/x-ns-proxy-autoconfig',
  1055. 'nwc' => 'application/x-nwc',
  1056. 'o' => 'application/x-object',
  1057. 'oza' => 'application/x-oz-application',
  1058. 'pl' => 'application/x-perl',
  1059. 'pm' => 'application/x-perl',
  1060. 'p7r' => 'application/x-pkcs7-certreqresp',
  1061. 'crl' => 'application/x-pkcs7-crl',
  1062. 'qtl' => 'application/x-quicktimeplayer',
  1063. 'rpm' => 'audio/x-pn-realaudio-plugin',
  1064. 'shar' => 'application/x-shar',
  1065. 'swf' => 'application/x-shockwave-flash',
  1066. 'swfl' => 'application/x-shockwave-flash',
  1067. 'sh' => 'text/x-sh',
  1068. 'sit' => 'application/x-stuffit',
  1069. 'sv4cpio' => 'application/x-sv4cpio',
  1070. 'sv4crc' => 'application/x-sv4crc',
  1071. 'tar' => 'application/x-tar',
  1072. 'tcl' => 'text/x-tcl',
  1073. 'tex' => 'text/x-tex',
  1074. 'gf' => 'application/x-tex-gf',
  1075. 'pk' => 'application/x-tex-pk',
  1076. 'texinfo' => 'application/x-texinfo',
  1077. 'texi' => 'application/x-texinfo',
  1078. '~' => 'application/x-trash',
  1079. '%' => 'application/x-trash',
  1080. 'bak' => 'application/x-trash',
  1081. 'old' => 'application/x-trash',
  1082. 'sik' => 'application/x-trash',
  1083. 't' => 'application/x-troff',
  1084. 'tr' => 'application/x-troff',
  1085. 'roff' => 'application/x-troff',
  1086. 'man' => 'application/x-troff-man',
  1087. 'me' => 'application/x-troff-me',
  1088. 'ms' => 'application/x-troff-ms',
  1089. 'ustar' => 'application/x-ustar',
  1090. 'src' => 'application/x-wais-source',
  1091. 'wz' => 'application/x-wingz',
  1092. 'crt' => 'application/x-x509-ca-cert',
  1093. 'fig' => 'application/x-xfig',
  1094. 'au' => 'audio/basic',
  1095. 'snd' => 'audio/basic',
  1096. 'mid' => 'audio/midi',
  1097. 'midi' => 'audio/midi',
  1098. 'kar' => 'audio/midi',
  1099. 'mpga' => 'audio/mpeg',
  1100. 'mpega' => 'audio/mpeg',
  1101. 'mp2' => 'audio/mpeg',
  1102. 'mp3' => 'audio/mpeg',
  1103. 'mp4' => 'video/mp4',
  1104. 'm3u' => 'audio/x-mpegurl',
  1105. 'sid' => 'audio/prs.sid',
  1106. 'aif' => 'audio/x-aiff',
  1107. 'aiff' => 'audio/x-aiff',
  1108. 'aifc' => 'audio/x-aiff',
  1109. 'gsm' => 'audio/x-gsm',
  1110. 'wma' => 'audio/x-ms-wma',
  1111. 'wax' => 'audio/x-ms-wax',
  1112. 'ra' => 'audio/x-realaudio',
  1113. 'rm' => 'audio/x-pn-realaudio',
  1114. 'ram' => 'audio/x-pn-realaudio',
  1115. 'pls' => 'audio/x-scpls',
  1116. 'sd2' => 'audio/x-sd2',
  1117. 'wav' => 'audio/x-wav',
  1118. 'pdb' => 'chemical/x-pdb',
  1119. 'xyz' => 'chemical/x-xyz',
  1120. 'bmp' => 'image/x-ms-bmp',
  1121. 'gif' => 'image/gif',
  1122. 'ief' => 'image/ief',
  1123. 'jpeg' => 'image/jpeg',
  1124. 'jpg' => 'image/jpeg',
  1125. 'jpe' => 'image/jpeg',
  1126. 'pcx' => 'image/pcx',
  1127. 'png' => 'image/png',
  1128. 'svg' => 'image/svg+xml',
  1129. 'svgz' => 'image/svg+xml',
  1130. 'tiff' => 'image/tiff',
  1131. 'tif' => 'image/tiff',
  1132. 'wbmp' => 'image/vnd.wap.wbmp',
  1133. 'ras' => 'image/x-cmu-raster',
  1134. 'cdr' => 'image/x-coreldraw',
  1135. 'pat' => 'image/x-coreldrawpattern',
  1136. 'cdt' => 'image/x-coreldrawtemplate',
  1137. 'djvu' => 'image/x-djvu',
  1138. 'djv' => 'image/x-djvu',
  1139. 'ico' => 'image/x-icon',
  1140. 'art' => 'image/x-jg',
  1141. 'jng' => 'image/x-jng',
  1142. 'psd' => 'image/x-photoshop',
  1143. 'pnm' => 'image/x-portable-anymap',
  1144. 'pbm' => 'image/x-portable-bitmap',
  1145. 'pgm' => 'image/x-portable-graymap',
  1146. 'ppm' => 'image/x-portable-pixmap',
  1147. 'rgb' => 'image/x-rgb',
  1148. 'xbm' => 'image/x-xbitmap',
  1149. 'xpm' => 'image/x-xpixmap',
  1150. 'xwd' => 'image/x-xwindowdump',
  1151. 'igs' => 'model/iges',
  1152. 'iges' => 'model/iges',
  1153. 'msh' => 'model/mesh',
  1154. 'mesh' => 'model/mesh',
  1155. 'silo' => 'model/mesh',
  1156. 'wrl' => 'x-world/x-vrml',
  1157. 'vrml' => 'x-world/x-vrml',
  1158. 'csv' => 'text/comma-separated-values',
  1159. 'css' => 'text/css',
  1160. '323' => 'text/h323',
  1161. 'htm' => 'text/html',
  1162. 'html' => 'text/html',
  1163. 'uls' => 'text/iuls',
  1164. 'mml' => 'text/mathml',
  1165. 'asc' => 'text/plain',
  1166. 'txt' => 'text/plain',
  1167. 'text' => 'text/plain',
  1168. 'diff' => 'text/plain',
  1169. 'rtx' => 'text/richtext',
  1170. 'sct' => 'text/scriptlet',
  1171. 'wsc' => 'text/scriptlet',
  1172. 'tm' => 'text/texmacs',
  1173. 'ts' => 'text/texmacs',
  1174. 'tsv' => 'text/tab-separated-values',
  1175. 'jad' => 'text/vnd.sun.j2me.app-descriptor',
  1176. 'wml' => 'text/vnd.wap.wml',
  1177. 'wmls' => 'text/vnd.wap.wmlscript',
  1178. 'xml' => 'text/xml',
  1179. 'xsl' => 'text/xml',
  1180. 'h++' => 'text/x-c++hdr',
  1181. 'hpp' => 'text/x-c++hdr',
  1182. 'hxx' => 'text/x-c++hdr',
  1183. 'hh' => 'text/x-c++hdr',
  1184. 'c++' => 'text/x-c++src',
  1185. 'cpp' => 'text/x-c++src',
  1186. 'cxx' => 'text/x-c++src',
  1187. 'cc' => 'text/x-c++src',
  1188. 'h' => 'text/x-chdr',
  1189. 'c' => 'text/x-csrc',
  1190. 'java' => 'text/x-java',
  1191. 'moc' => 'text/x-moc',
  1192. 'p' => 'text/x-pascal',
  1193. 'pas' => 'text/x-pascal',
  1194. '***' => 'text/x-pcs-***',
  1195. 'shtml' => 'text/x-server-parsed-html',
  1196. 'etx' => 'text/x-setext',
  1197. 'tk' => 'text/x-tcl',
  1198. 'ltx' => 'text/x-tex',
  1199. 'sty' => 'text/x-tex',
  1200. 'cls' => 'text/x-tex',
  1201. 'vcs' => 'text/x-vcalendar',
  1202. 'vcf' => 'text/x-vcard',
  1203. 'dl' => 'video/dl',
  1204. 'fli' => 'video/fli',
  1205. 'gl' => 'video/gl',
  1206. 'mpeg' => 'video/mpeg',
  1207. 'mpg' => 'video/mpeg',
  1208. 'mpe' => 'video/mpeg',
  1209. 'qt' => 'video/quicktime',
  1210. 'mov' => 'video/quicktime',
  1211. 'mxu' => 'video/vnd.mpegurl',
  1212. 'dif' => 'video/x-dv',
  1213. 'dv' => 'video/x-dv',
  1214. 'lsf' => 'video/x-la-asf',
  1215. 'lsx' => 'video/x-la-asf',
  1216. 'mng' => 'video/x-mng',
  1217. 'asf' => 'video/x-ms-asf',
  1218. 'asx' => 'video/x-ms-asf',
  1219. 'wm' => 'video/x-ms-wm',
  1220. 'wmv' => 'video/x-ms-wmv',
  1221. 'wmx' => 'video/x-ms-wmx',
  1222. 'wvx' => 'video/x-ms-wvx',
  1223. 'avi' => 'video/x-msvideo',
  1224. 'movie' => 'video/x-sgi-movie',
  1225. 'ice' => 'x-conference/x-cooltalk',
  1226. 'vrm' => 'x-world/x-vrml',
  1227. 'rar' => 'application/x-rar-compressed',
  1228. 'cab' => 'application/vnd.ms-cab-compressed',
  1229. 'webp' => 'image/webp'
  1230. ];
  1231. $part = explode('.', $fileName);
  1232. $size = count($part);
  1233. if ($size > 1) {
  1234. $ext = $part[$size - 1];
  1235. if (isset($mimeTypes[$ext])) {
  1236. return $mimeTypes[$ext];
  1237. }
  1238. }
  1239. return 'application/octet-stream';
  1240. }
  1241. /**
  1242. * 寻找匹配的mime图标
  1243. *
  1244. * @access public
  1245. *
  1246. * @param string $mime mime类型
  1247. *
  1248. * @return string
  1249. */
  1250. public static function mimeIconType(string $mime): string
  1251. {
  1252. $parts = explode('/', $mime);
  1253. if (count($parts) < 2) {
  1254. return 'unknown';
  1255. }
  1256. [$type, $stream] = $parts;
  1257. if (in_array($type, ['image', 'video', 'audio', 'text', 'application'])) {
  1258. switch (true) {
  1259. case in_array($stream, ['msword', 'msaccess', 'ms-powerpoint', 'ms-powerpoint']):
  1260. case 0 === strpos($stream, 'vnd.'):
  1261. return 'office';
  1262. case false !== strpos($stream, 'html')
  1263. || false !== strpos($stream, 'xml')
  1264. || false !== strpos($stream, 'wml'):
  1265. return 'html';
  1266. case false !== strpos($stream, 'compressed')
  1267. || false !== strpos($stream, 'zip')
  1268. || in_array($stream, ['application/x-gtar', 'application/x-tar']):
  1269. return 'archive';
  1270. case 'text' == $type && 0 === strpos($stream, 'x-'):
  1271. return 'script';
  1272. default:
  1273. return $type;
  1274. }
  1275. } else {
  1276. return 'unknown';
  1277. }
  1278. }
  1279. /**
  1280. * 解析属性
  1281. *
  1282. * @param string $attrs 属性字符串
  1283. * @return array
  1284. */
  1285. private static function parseAttrs(string $attrs): array
  1286. {
  1287. $attrs = trim($attrs);
  1288. $len = strlen($attrs);
  1289. $pos = -1;
  1290. $result = [];
  1291. $quote = '';
  1292. $key = '';
  1293. $value = '';
  1294. for ($i = 0; $i < $len; $i++) {
  1295. if ('=' != $attrs[$i] && !ctype_space($attrs[$i]) && -1 == $pos) {
  1296. $key .= $attrs[$i];
  1297. /** 最后一个 */
  1298. if ($i == $len - 1) {
  1299. if ('' != ($key = trim($key))) {
  1300. $result[$key] = '';
  1301. $key = '';
  1302. $value = '';
  1303. }
  1304. }
  1305. } elseif (ctype_space($attrs[$i]) && -1 == $pos) {
  1306. $pos = -2;
  1307. } elseif ('=' == $attrs[$i] && 0 > $pos) {
  1308. $pos = 0;
  1309. } elseif (('"' == $attrs[$i] || "'" == $attrs[$i]) && 0 == $pos) {
  1310. $quote = $attrs[$i];
  1311. $value .= $attrs[$i];
  1312. $pos = 1;
  1313. } elseif ($quote != $attrs[$i] && 1 == $pos) {
  1314. $value .= $attrs[$i];
  1315. } elseif ($quote == $attrs[$i] && 1 == $pos) {
  1316. $pos = -1;
  1317. $value .= $attrs[$i];
  1318. $result[trim($key)] = $value;
  1319. $key = '';
  1320. $value = '';
  1321. } elseif ('=' != $attrs[$i] && !ctype_space($attrs[$i]) && -2 == $pos) {
  1322. if ('' != ($key = trim($key))) {
  1323. $result[$key] = '';
  1324. }
  1325. $key = '';
  1326. $value = '';
  1327. $pos = -1;
  1328. $key .= $attrs[$i];
  1329. }
  1330. }
  1331. return $result;
  1332. }
  1333. }
  1334. }