Utils.php 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  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. * @param string $text Create a hash from this text.
  35. *
  36. * @return string generated small hash.
  37. */
  38. function smallHash($text)
  39. {
  40. $t = rtrim(base64_encode(hash('crc32', $text, true)), '=');
  41. return strtr($t, '+/', '-_');
  42. }
  43. /**
  44. * Tells if a string start with a substring
  45. *
  46. * @param string $haystack Given string.
  47. * @param string $needle String to search at the beginning of $haystack.
  48. * @param bool $case Case sensitive.
  49. *
  50. * @return bool True if $haystack starts with $needle.
  51. */
  52. function startsWith($haystack, $needle, $case = true)
  53. {
  54. if ($case) {
  55. return (strcmp(substr($haystack, 0, strlen($needle)), $needle) === 0);
  56. }
  57. return (strcasecmp(substr($haystack, 0, strlen($needle)), $needle) === 0);
  58. }
  59. /**
  60. * Tells if a string ends with a substring
  61. *
  62. * @param string $haystack Given string.
  63. * @param string $needle String to search at the end of $haystack.
  64. * @param bool $case Case sensitive.
  65. *
  66. * @return bool True if $haystack ends with $needle.
  67. */
  68. function endsWith($haystack, $needle, $case = true)
  69. {
  70. if ($case) {
  71. return (strcmp(substr($haystack, strlen($haystack) - strlen($needle)), $needle) === 0);
  72. }
  73. return (strcasecmp(substr($haystack, strlen($haystack) - strlen($needle)), $needle) === 0);
  74. }
  75. /**
  76. * Htmlspecialchars wrapper
  77. * Support multidimensional array of strings.
  78. *
  79. * @param mixed $input Data to escape: a single string or an array of strings.
  80. *
  81. * @return string escaped.
  82. */
  83. function escape($input)
  84. {
  85. if (is_array($input)) {
  86. $out = array();
  87. foreach($input as $key => $value) {
  88. $out[$key] = escape($value);
  89. }
  90. return $out;
  91. }
  92. return htmlspecialchars($input, ENT_COMPAT, 'UTF-8', false);
  93. }
  94. /**
  95. * Reverse the escape function.
  96. *
  97. * @param string $str the string to unescape.
  98. *
  99. * @return string unescaped string.
  100. */
  101. function unescape($str)
  102. {
  103. return htmlspecialchars_decode($str);
  104. }
  105. /**
  106. * Sanitize link before rendering.
  107. *
  108. * @param array $link Link to escape.
  109. */
  110. function sanitizeLink(&$link)
  111. {
  112. $link['url'] = escape($link['url']); // useful?
  113. $link['title'] = escape($link['title']);
  114. $link['description'] = escape($link['description']);
  115. $link['tags'] = escape($link['tags']);
  116. }
  117. /**
  118. * Checks if a string represents a valid date
  119. * @param string $format The expected DateTime format of the string
  120. * @param string $string A string-formatted date
  121. *
  122. * @return bool whether the string is a valid date
  123. *
  124. * @see http://php.net/manual/en/class.datetime.php
  125. * @see http://php.net/manual/en/datetime.createfromformat.php
  126. */
  127. function checkDateFormat($format, $string)
  128. {
  129. $date = DateTime::createFromFormat($format, $string);
  130. return $date && $date->format($string) == $string;
  131. }
  132. /**
  133. * Generate a header location from HTTP_REFERER.
  134. * Make sure the referer is Shaarli itself and prevent redirection loop.
  135. *
  136. * @param string $referer - HTTP_REFERER.
  137. * @param string $host - Server HOST.
  138. * @param array $loopTerms - Contains list of term to prevent redirection loop.
  139. *
  140. * @return string $referer - final referer.
  141. */
  142. function generateLocation($referer, $host, $loopTerms = array())
  143. {
  144. $finalReferer = '?';
  145. // No referer if it contains any value in $loopCriteria.
  146. foreach ($loopTerms as $value) {
  147. if (strpos($referer, $value) !== false) {
  148. return $finalReferer;
  149. }
  150. }
  151. // Remove port from HTTP_HOST
  152. if ($pos = strpos($host, ':')) {
  153. $host = substr($host, 0, $pos);
  154. }
  155. $refererHost = parse_url($referer, PHP_URL_HOST);
  156. if (!empty($referer) && (strpos($refererHost, $host) !== false || startsWith('?', $refererHost))) {
  157. $finalReferer = $referer;
  158. }
  159. return $finalReferer;
  160. }
  161. /**
  162. * Validate session ID to prevent Full Path Disclosure.
  163. *
  164. * See #298.
  165. * The session ID's format depends on the hash algorithm set in PHP settings
  166. *
  167. * @param string $sessionId Session ID
  168. *
  169. * @return true if valid, false otherwise.
  170. *
  171. * @see http://php.net/manual/en/function.hash-algos.php
  172. * @see http://php.net/manual/en/session.configuration.php
  173. */
  174. function is_session_id_valid($sessionId)
  175. {
  176. if (empty($sessionId)) {
  177. return false;
  178. }
  179. if (!$sessionId) {
  180. return false;
  181. }
  182. if (!preg_match('/^[a-zA-Z0-9,-]{2,128}$/', $sessionId)) {
  183. return false;
  184. }
  185. return true;
  186. }
  187. /**
  188. * Sniff browser language to set the locale automatically.
  189. * Note that is may not work on your server if the corresponding locale is not installed.
  190. *
  191. * @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").
  192. **/
  193. function autoLocale($headerLocale)
  194. {
  195. // Default if browser does not send HTTP_ACCEPT_LANGUAGE
  196. $attempts = array('en_US');
  197. if (isset($headerLocale)) {
  198. // (It's a bit crude, but it works very well. Preferred language is always presented first.)
  199. if (preg_match('/([a-z]{2})-?([a-z]{2})?/i', $headerLocale, $matches)) {
  200. $loc = $matches[1] . (!empty($matches[2]) ? '_' . strtoupper($matches[2]) : '');
  201. $attempts = array(
  202. $loc.'.UTF-8', $loc, str_replace('_', '-', $loc).'.UTF-8', str_replace('_', '-', $loc),
  203. $loc . '_' . strtoupper($loc).'.UTF-8', $loc . '_' . strtoupper($loc),
  204. $loc . '_' . $loc.'.UTF-8', $loc . '_' . $loc, $loc . '-' . strtoupper($loc).'.UTF-8',
  205. $loc . '-' . strtoupper($loc), $loc . '-' . $loc.'.UTF-8', $loc . '-' . $loc
  206. );
  207. }
  208. }
  209. setlocale(LC_ALL, $attempts);
  210. }