Utils.php 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. <?php
  2. /**
  3. * Shaarli utilities
  4. */
  5. /**
  6. * Logs a message to a text file
  7. *
  8. * The log format is compatible with fail2ban.
  9. *
  10. * @param string $logFile where to write the logs
  11. * @param string $clientIp the client's remote IPv4/IPv6 address
  12. * @param string $message the message to log
  13. */
  14. function logm($logFile, $clientIp, $message)
  15. {
  16. file_put_contents(
  17. $logFile,
  18. date('Y/m/d H:i:s').' - '.$clientIp.' - '.strval($message).PHP_EOL,
  19. FILE_APPEND
  20. );
  21. }
  22. /**
  23. * Returns the small hash of a string, using RFC 4648 base64url format
  24. *
  25. * Small hashes:
  26. * - are unique (well, as unique as crc32, at last)
  27. * - are always 6 characters long.
  28. * - only use the following characters: a-z A-Z 0-9 - _ @
  29. * - are NOT cryptographically secure (they CAN be forged)
  30. *
  31. * In Shaarli, they are used as a tinyurl-like link to individual entries,
  32. * e.g. smallHash('20111006_131924') --> yZH23w
  33. */
  34. function smallHash($text)
  35. {
  36. $t = rtrim(base64_encode(hash('crc32', $text, true)), '=');
  37. return strtr($t, '+/', '-_');
  38. }
  39. /**
  40. * Tells if a string start with a substring
  41. *
  42. * @param string $haystack Given string.
  43. * @param string $needle String to search at the beginning of $haystack.
  44. * @param bool $case Case sensitive.
  45. *
  46. * @return bool True if $haystack starts with $needle.
  47. */
  48. function startsWith($haystack, $needle, $case = true)
  49. {
  50. if ($case) {
  51. return (strcmp(substr($haystack, 0, strlen($needle)), $needle) === 0);
  52. }
  53. return (strcasecmp(substr($haystack, 0, strlen($needle)), $needle) === 0);
  54. }
  55. /**
  56. * Tells if a string ends with a substring
  57. *
  58. * @param string $haystack Given string.
  59. * @param string $needle String to search at the end of $haystack.
  60. * @param bool $case Case sensitive.
  61. *
  62. * @return bool True if $haystack ends with $needle.
  63. */
  64. function endsWith($haystack, $needle, $case = true)
  65. {
  66. if ($case) {
  67. return (strcmp(substr($haystack, strlen($haystack) - strlen($needle)), $needle) === 0);
  68. }
  69. return (strcasecmp(substr($haystack, strlen($haystack) - strlen($needle)), $needle) === 0);
  70. }
  71. /**
  72. * Htmlspecialchars wrapper
  73. * Support multidimensional array of strings.
  74. *
  75. * @param mixed $input Data to escape: a single string or an array of strings.
  76. *
  77. * @return string escaped.
  78. */
  79. function escape($input)
  80. {
  81. if (is_array($input)) {
  82. $out = array();
  83. foreach($input as $key => $value) {
  84. $out[$key] = escape($value);
  85. }
  86. return $out;
  87. }
  88. return htmlspecialchars($input, ENT_COMPAT, 'UTF-8', false);
  89. }
  90. /**
  91. * Reverse the escape function.
  92. *
  93. * @param string $str the string to unescape.
  94. *
  95. * @return string unescaped string.
  96. */
  97. function unescape($str)
  98. {
  99. return htmlspecialchars_decode($str);
  100. }
  101. /**
  102. * Link sanitization before templating
  103. */
  104. function sanitizeLink(&$link)
  105. {
  106. $link['url'] = escape($link['url']); // useful?
  107. $link['title'] = escape($link['title']);
  108. $link['description'] = escape($link['description']);
  109. $link['tags'] = escape($link['tags']);
  110. }
  111. /**
  112. * Checks if a string represents a valid date
  113. * @param string $format The expected DateTime format of the string
  114. * @param string $string A string-formatted date
  115. *
  116. * @return bool whether the string is a valid date
  117. *
  118. * @see http://php.net/manual/en/class.datetime.php
  119. * @see http://php.net/manual/en/datetime.createfromformat.php
  120. */
  121. function checkDateFormat($format, $string)
  122. {
  123. $date = DateTime::createFromFormat($format, $string);
  124. return $date && $date->format($string) == $string;
  125. }
  126. /**
  127. * Generate a header location from HTTP_REFERER.
  128. * Make sure the referer is Shaarli itself and prevent redirection loop.
  129. *
  130. * @param string $referer - HTTP_REFERER.
  131. * @param string $host - Server HOST.
  132. * @param array $loopTerms - Contains list of term to prevent redirection loop.
  133. *
  134. * @return string $referer - final referer.
  135. */
  136. function generateLocation($referer, $host, $loopTerms = array())
  137. {
  138. $finalReferer = '?';
  139. // No referer if it contains any value in $loopCriteria.
  140. foreach ($loopTerms as $value) {
  141. if (strpos($referer, $value) !== false) {
  142. return $finalReferer;
  143. }
  144. }
  145. // Remove port from HTTP_HOST
  146. if ($pos = strpos($host, ':')) {
  147. $host = substr($host, 0, $pos);
  148. }
  149. $refererHost = parse_url($referer, PHP_URL_HOST);
  150. if (!empty($referer) && (strpos($refererHost, $host) !== false || startsWith('?', $refererHost))) {
  151. $finalReferer = $referer;
  152. }
  153. return $finalReferer;
  154. }
  155. /**
  156. * Validate session ID to prevent Full Path Disclosure.
  157. *
  158. * See #298.
  159. * The session ID's format depends on the hash algorithm set in PHP settings
  160. *
  161. * @param string $sessionId Session ID
  162. *
  163. * @return true if valid, false otherwise.
  164. *
  165. * @see http://php.net/manual/en/function.hash-algos.php
  166. * @see http://php.net/manual/en/session.configuration.php
  167. */
  168. function is_session_id_valid($sessionId)
  169. {
  170. if (empty($sessionId)) {
  171. return false;
  172. }
  173. if (!$sessionId) {
  174. return false;
  175. }
  176. if (!preg_match('/^[a-zA-Z0-9,-]{2,128}$/', $sessionId)) {
  177. return false;
  178. }
  179. return true;
  180. }
  181. /**
  182. * Sniff browser language to set the locale automatically.
  183. * Note that is may not work on your server if the corresponding locale is not installed.
  184. *
  185. * @param string $headerLocale Locale send in HTTP headers (e.g. "fr,fr-fr;q=0.8,en;q=0.5,en-us;q=0.3").
  186. **/
  187. function autoLocale($headerLocale)
  188. {
  189. // Default if browser does not send HTTP_ACCEPT_LANGUAGE
  190. $attempts = array('en_US');
  191. if (isset($headerLocale)) {
  192. // (It's a bit crude, but it works very well. Preferred language is always presented first.)
  193. if (preg_match('/([a-z]{2})-?([a-z]{2})?/i', $headerLocale, $matches)) {
  194. $loc = $matches[1] . (!empty($matches[2]) ? '_' . strtoupper($matches[2]) : '');
  195. $attempts = array(
  196. $loc.'.UTF-8', $loc, str_replace('_', '-', $loc).'.UTF-8', str_replace('_', '-', $loc),
  197. $loc . '_' . strtoupper($loc).'.UTF-8', $loc . '_' . strtoupper($loc),
  198. $loc . '_' . $loc.'.UTF-8', $loc . '_' . $loc, $loc . '-' . strtoupper($loc).'.UTF-8',
  199. $loc . '-' . strtoupper($loc), $loc . '-' . $loc.'.UTF-8', $loc . '-' . $loc
  200. );
  201. }
  202. }
  203. setlocale(LC_ALL, $attempts);
  204. }