PageRenderTime 54ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/base/lib/flourishlib/fSession.php

https://bitbucket.org/thanhtungnguyenphp/monitos
PHP | 682 lines | 369 code | 98 blank | 215 comment | 52 complexity | d453c51f04e518ccc8853d307ae7adf4 MD5 | raw file
  1. <?php
  2. /**
  3. * Wraps the session control functions and the `$_SESSION` superglobal for a more consistent and safer API
  4. *
  5. * A `Cannot send session cache limiter` warning will be triggered if ::open(),
  6. * ::add(), ::clear(), ::delete(), ::get() or ::set() is called after output has
  7. * been sent to the browser. To prevent such a warning, explicitly call ::open()
  8. * before generating any output.
  9. *
  10. * @copyright Copyright (c) 2007-2011 Will Bond, others
  11. * @author Will Bond [wb] <will@flourishlib.com>
  12. * @author Alex Leeds [al] <alex@kingleeds.com>
  13. * @license http://flourishlib.com/license
  14. *
  15. * @package Flourish
  16. * @link http://flourishlib.com/fSession
  17. *
  18. * @version 1.0.0b17
  19. * @changes 1.0.0b17 Updated ::ignoreSubdomain() to use `$_SERVER['HTTP_HOST']` when `$_SERVER['SERVER_NAME']` is not set [wb, 2011-02-01]
  20. * @changes 1.0.0b16 Changed ::delete() to return the value of the key being deleted [wb, 2010-09-19]
  21. * @changes 1.0.0b15 Added documentation about `[sub-key]` syntax [wb, 2010-09-12]
  22. * @changes 1.0.0b14 Backwards Compatibility Break - ::add(), ::delete(), ::get() and ::set() now interpret `[` and `]` as array shorthand and thus they can not be used in keys - added `$beginning` parameter to ::add(), added ::remove() method [wb, 2010-09-12]
  23. * @changes 1.0.0b13 Fixed a bug that prevented working with existing sessions since they did not have the `fSession::expires` key [wb, 2010-08-24]
  24. * @changes 1.0.0b12 Changed ::enablePersistence() to always regenerate the session ID, which ensures the function works even if the ID has already been regenerated by fAuthorizaton [wb, 2010-08-21]
  25. * @changes 1.0.0b11 Updated the class to make sure ::enablePersistence() is called after ::ignoreSubdomain(), ::setLength() and ::setPath() [wb, 2010-05-29]
  26. * @changes 1.0.0b10 Fixed some documentation bugs [wb, 2010-03-03]
  27. * @changes 1.0.0b9 Fixed a bug in ::destroy() where sessions weren't always being properly destroyed [wb, 2009-12-08]
  28. * @changes 1.0.0b8 Fixed a bug that made the unit tests fail on PHP 5.1 [wb, 2009-10-27]
  29. * @changes 1.0.0b7 Backwards Compatibility Break - Removed the `$prefix` parameter from the methods ::delete(), ::get() and ::set() - added the methods ::add(), ::enablePersistence(), ::regenerateID() [wb+al, 2009-10-23]
  30. * @changes 1.0.0b6 Backwards Compatibility Break - the first parameter of ::clear() was removed, use ::delete() instead [wb, 2009-05-08]
  31. * @changes 1.0.0b5 Added documentation about session cache limiter warnings [wb, 2009-05-04]
  32. * @changes 1.0.0b4 The class now works with existing sessions [wb, 2009-05-04]
  33. * @changes 1.0.0b3 Fixed ::clear() to properly handle when `$key` is `NULL` [wb, 2009-02-05]
  34. * @changes 1.0.0b2 Made ::open() public, fixed some consistency issues with setting session options through the class [wb, 2009-01-06]
  35. * @changes 1.0.0b The initial implementation [wb, 2007-06-14]
  36. */
  37. class fSession
  38. {
  39. // The following constants allow for nice looking callbacks to static methods
  40. const add = 'fSession::add';
  41. const clear = 'fSession::clear';
  42. const close = 'fSession::close';
  43. const delete = 'fSession::delete';
  44. const destroy = 'fSession::destroy';
  45. const enablePersistence = 'fSession::enablePersistence';
  46. const get = 'fSession::get';
  47. const ignoreSubdomain = 'fSession::ignoreSubdomain';
  48. const open = 'fSession::open';
  49. const regenerateID = 'fSession::regenerateID';
  50. const reset = 'fSession::reset';
  51. const set = 'fSession::set';
  52. const setLength = 'fSession::setLength';
  53. const setPath = 'fSession::setPath';
  54. /**
  55. * The length for a normal session
  56. *
  57. * @var integer
  58. */
  59. static private $normal_timespan = NULL;
  60. /**
  61. * If the session is open
  62. *
  63. * @var boolean
  64. */
  65. static private $open = FALSE;
  66. /**
  67. * The length for a persistent session cookie - one that survives browser restarts
  68. *
  69. * @var integer
  70. */
  71. static private $persistent_timespan = NULL;
  72. /**
  73. * If the session ID was regenerated during this script
  74. *
  75. * @var boolean
  76. */
  77. static private $regenerated = FALSE;
  78. /**
  79. * Adds a value to an already-existing array value, or to a new array value
  80. *
  81. * @param string $key The name to access the array under - array elements can be modified via `[sub-key]` syntax, and thus `[` and `]` can not be used in key names
  82. * @param mixed $value The value to add to the array
  83. * @param boolean $beginning If the value should be added to the beginning
  84. * @return void
  85. */
  86. static public function add($key, $value, $beginning=FALSE)
  87. {
  88. self::open();
  89. $tip =& $_SESSION;
  90. if ($bracket_pos = strpos($key, '[')) {
  91. $original_key = $key;
  92. $array_dereference = substr($key, $bracket_pos);
  93. $key = substr($key, 0, $bracket_pos);
  94. preg_match_all('#(?<=\[)[^\[\]]+(?=\])#', $array_dereference, $array_keys, PREG_SET_ORDER);
  95. $array_keys = array_map('current', $array_keys);
  96. array_unshift($array_keys, $key);
  97. foreach (array_slice($array_keys, 0, -1) as $array_key) {
  98. if (!isset($tip[$array_key])) {
  99. $tip[$array_key] = array();
  100. } elseif (!is_array($tip[$array_key])) {
  101. throw new fProgrammerException(
  102. '%1$s was called for the key, %2$s, which is not an array',
  103. __CLASS__ . '::add()',
  104. $original_key
  105. );
  106. }
  107. $tip =& $tip[$array_key];
  108. }
  109. $key = end($array_keys);
  110. }
  111. if (!isset($tip[$key])) {
  112. $tip[$key] = array();
  113. } elseif (!is_array($tip[$key])) {
  114. throw new fProgrammerException(
  115. '%1$s was called for the key, %2$s, which is not an array',
  116. __CLASS__ . '::add()',
  117. $key
  118. );
  119. }
  120. if ($beginning) {
  121. array_unshift($tip[$key], $value);
  122. } else {
  123. $tip[$key][] = $value;
  124. }
  125. }
  126. /**
  127. * Removes all session values with the provided prefix
  128. *
  129. * This method will not remove session variables used by this class, which
  130. * are prefixed with `fSession::`.
  131. *
  132. * @param string $prefix The prefix to clear all session values for
  133. * @return void
  134. */
  135. static public function clear($prefix=NULL)
  136. {
  137. self::open();
  138. $session_type = $_SESSION['fSession::type'];
  139. $session_expires = $_SESSION['fSession::expires'];
  140. if ($prefix) {
  141. foreach ($_SESSION as $key => $value) {
  142. if (strpos($key, $prefix) === 0) {
  143. unset($_SESSION[$key]);
  144. }
  145. }
  146. } else {
  147. $_SESSION = array();
  148. }
  149. $_SESSION['fSession::type'] = $session_type;
  150. $_SESSION['fSession::expires'] = $session_expires;
  151. }
  152. /**
  153. * Closes the session for writing, allowing other pages to open the session
  154. *
  155. * @return void
  156. */
  157. static public function close()
  158. {
  159. if (!self::$open) { return; }
  160. session_write_close();
  161. unset($_SESSION);
  162. self::$open = FALSE;
  163. }
  164. /**
  165. * Deletes a value from the session
  166. *
  167. * @param string $key The key of the value to delete - array elements can be modified via `[sub-key]` syntax, and thus `[` and `]` can not be used in key names
  168. * @param mixed $default_value The value to return if the `$key` is not set
  169. * @return mixed The value of the `$key` that was deleted
  170. */
  171. static public function delete($key, $default_value=NULL)
  172. {
  173. self::open();
  174. $value = $default_value;
  175. if ($bracket_pos = strpos($key, '[')) {
  176. $original_key = $key;
  177. $array_dereference = substr($key, $bracket_pos);
  178. $key = substr($key, 0, $bracket_pos);
  179. if (!isset($_SESSION[$key])) {
  180. return $value;
  181. }
  182. preg_match_all('#(?<=\[)[^\[\]]+(?=\])#', $array_dereference, $array_keys, PREG_SET_ORDER);
  183. $array_keys = array_map('current', $array_keys);
  184. $tip =& $_SESSION[$key];
  185. foreach (array_slice($array_keys, 0, -1) as $array_key) {
  186. if (!isset($tip[$array_key])) {
  187. return $value;
  188. } elseif (!is_array($tip[$array_key])) {
  189. throw new fProgrammerException(
  190. '%1$s was called for an element, %2$s, which is not an array',
  191. __CLASS__ . '::delete()',
  192. $original_key
  193. );
  194. }
  195. $tip =& $tip[$array_key];
  196. }
  197. $key = end($array_keys);
  198. } else {
  199. $tip =& $_SESSION;
  200. }
  201. if (isset($tip[$key])) {
  202. $value = $tip[$key];
  203. unset($tip[$key]);
  204. }
  205. return $value;
  206. }
  207. /**
  208. * Destroys the session, removing all values
  209. *
  210. * @return void
  211. */
  212. static public function destroy()
  213. {
  214. self::open();
  215. $_SESSION = array();
  216. if (isset($_COOKIE[session_name()])) {
  217. $params = session_get_cookie_params();
  218. setcookie(session_name(), '', time()-43200, $params['path'], $params['domain'], $params['secure']);
  219. }
  220. session_destroy();
  221. self::regenerateID();
  222. }
  223. /**
  224. * Changed the session to use a time-based cookie instead of a session-based cookie
  225. *
  226. * The length of the time-based cookie is controlled by ::setLength(). When
  227. * this method is called, a time-based cookie is used to store the session
  228. * ID. This means the session can persist browser restarts. Normally, a
  229. * session-based cookie is used, which is wiped when a browser restart
  230. * occurs.
  231. *
  232. * This method should be called during the login process and will normally
  233. * be controlled by a checkbox or similar where the user can indicate if
  234. * they want to stay logged in for an extended period of time.
  235. *
  236. * @return void
  237. */
  238. static public function enablePersistence()
  239. {
  240. if (self::$persistent_timespan === NULL) {
  241. throw new fProgrammerException(
  242. 'The method %1$s must be called with the %2$s parameter before calling %3$s',
  243. __CLASS__ . '::setLength()',
  244. '$persistent_timespan',
  245. __CLASS__ . '::enablePersistence()'
  246. );
  247. }
  248. $current_params = session_get_cookie_params();
  249. $params = array(
  250. self::$persistent_timespan,
  251. $current_params['path'],
  252. $current_params['domain'],
  253. $current_params['secure']
  254. );
  255. call_user_func_array('session_set_cookie_params', $params);
  256. self::open();
  257. $_SESSION['fSession::type'] = 'persistent';
  258. session_regenerate_id();
  259. self::$regenerated = TRUE;
  260. }
  261. /**
  262. * Gets data from the `$_SESSION` superglobal
  263. *
  264. * @param string $key The name to get the value for - array elements can be accessed via `[sub-key]` syntax, and thus `[` and `]` can not be used in key names
  265. * @param mixed $default_value The default value to use if the requested key is not set
  266. * @return mixed The data element requested
  267. */
  268. static public function get($key, $default_value=NULL)
  269. {
  270. self::open();
  271. $array_dereference = NULL;
  272. if ($bracket_pos = strpos($key, '[')) {
  273. $array_dereference = substr($key, $bracket_pos);
  274. $key = substr($key, 0, $bracket_pos);
  275. }
  276. if (!isset($_SESSION[$key])) {
  277. return $default_value;
  278. }
  279. $value = $_SESSION[$key];
  280. if ($array_dereference) {
  281. preg_match_all('#(?<=\[)[^\[\]]+(?=\])#', $array_dereference, $array_keys, PREG_SET_ORDER);
  282. $array_keys = array_map('current', $array_keys);
  283. foreach ($array_keys as $array_key) {
  284. if (!is_array($value) || !isset($value[$array_key])) {
  285. $value = $default_value;
  286. break;
  287. }
  288. $value = $value[$array_key];
  289. }
  290. }
  291. return $value;
  292. }
  293. /**
  294. * Sets the session to run on the main domain, not just the specific subdomain currently being accessed
  295. *
  296. * This method should be called after any calls to
  297. * [http://php.net/session_set_cookie_params `session_set_cookie_params()`].
  298. *
  299. * @return void
  300. */
  301. static public function ignoreSubdomain()
  302. {
  303. if (self::$open || isset($_SESSION)) {
  304. throw new fProgrammerException(
  305. '%1$s must be called before any of %2$s, %3$s, %4$s, %5$s, %6$s, %7$s or %8$s',
  306. __CLASS__ . '::ignoreSubdomain()',
  307. __CLASS__ . '::add()',
  308. __CLASS__ . '::clear()',
  309. __CLASS__ . '::enablePersistence()',
  310. __CLASS__ . '::get()',
  311. __CLASS__ . '::open()',
  312. __CLASS__ . '::set()',
  313. 'session_start()'
  314. );
  315. }
  316. $current_params = session_get_cookie_params();
  317. if (isset($_SERVER['SERVER_NAME'])) {
  318. $domain = $_SERVER['SERVER_NAME'];
  319. } elseif (isset($_SERVER['HTTP_HOST'])) {
  320. $domain = $_SERVER['HTTP_HOST'];
  321. } else {
  322. throw new fEnvironmentException(
  323. 'The domain name could not be found in %1$s or %2$s. Please set one of these keys to use %3$s.',
  324. '$_SERVER[\'SERVER_NAME\']',
  325. '$_SERVER[\'HTTP_HOST\']',
  326. __CLASS__ . '::ignoreSubdomain()'
  327. );
  328. }
  329. $params = array(
  330. $current_params['lifetime'],
  331. $current_params['path'],
  332. preg_replace('#.*?([a-z0-9\\-]+\.[a-z]+)$#iD', '.\1', $domain),
  333. $current_params['secure']
  334. );
  335. call_user_func_array('session_set_cookie_params', $params);
  336. }
  337. /**
  338. * Opens the session for writing, is automatically called by ::clear(), ::get() and ::set()
  339. *
  340. * A `Cannot send session cache limiter` warning will be triggered if this,
  341. * ::add(), ::clear(), ::delete(), ::get() or ::set() is called after output
  342. * has been sent to the browser. To prevent such a warning, explicitly call
  343. * this method before generating any output.
  344. *
  345. * @param boolean $cookie_only_session_id If the session id should only be allowed via cookie - this is a security issue and should only be set to `FALSE` when absolutely necessary
  346. * @return void
  347. */
  348. static public function open($cookie_only_session_id=TRUE)
  349. {
  350. if (self::$open) { return; }
  351. self::$open = TRUE;
  352. if (self::$normal_timespan === NULL) {
  353. self::$normal_timespan = ini_get('session.gc_maxlifetime');
  354. }
  355. // If the session is already open, we just piggy-back without setting options
  356. if (!isset($_SESSION)) {
  357. if ($cookie_only_session_id) {
  358. ini_set('session.use_cookies', 1);
  359. ini_set('session.use_only_cookies', 1);
  360. }
  361. session_start();
  362. }
  363. // If the session has existed for too long, reset it
  364. if (isset($_SESSION['fSession::expires']) && $_SESSION['fSession::expires'] < $_SERVER['REQUEST_TIME']) {
  365. $_SESSION = array();
  366. self::regenerateID();
  367. }
  368. if (!isset($_SESSION['fSession::type'])) {
  369. $_SESSION['fSession::type'] = 'normal';
  370. }
  371. // We store the expiration time for a session to allow for both normal and persistent sessions
  372. if ($_SESSION['fSession::type'] == 'persistent' && self::$persistent_timespan) {
  373. $_SESSION['fSession::expires'] = $_SERVER['REQUEST_TIME'] + self::$persistent_timespan;
  374. } else {
  375. $_SESSION['fSession::expires'] = $_SERVER['REQUEST_TIME'] + self::$normal_timespan;
  376. }
  377. }
  378. /**
  379. * Regenerates the session ID, but only once per script execution
  380. *
  381. * @internal
  382. *
  383. * @return void
  384. */
  385. static public function regenerateID()
  386. {
  387. if (!self::$regenerated){
  388. session_regenerate_id();
  389. self::$regenerated = TRUE;
  390. }
  391. }
  392. /**
  393. * Removes and returns the value from the end of an array value
  394. *
  395. * @param string $key The name of the element to remove the value from - array elements can be modified via `[sub-key]` syntax, and thus `[` and `]` can not be used in key names
  396. * @param boolean $beginning If the value should be removed to the beginning
  397. * @return mixed The value that was removed
  398. */
  399. static public function remove($key, $beginning=FALSE)
  400. {
  401. self::open();
  402. $tip =& $_SESSION;
  403. if ($bracket_pos = strpos($key, '[')) {
  404. $original_key = $key;
  405. $array_dereference = substr($key, $bracket_pos);
  406. $key = substr($key, 0, $bracket_pos);
  407. preg_match_all('#(?<=\[)[^\[\]]+(?=\])#', $array_dereference, $array_keys, PREG_SET_ORDER);
  408. $array_keys = array_map('current', $array_keys);
  409. array_unshift($array_keys, $key);
  410. foreach (array_slice($array_keys, 0, -1) as $array_key) {
  411. if (!isset($tip[$array_key])) {
  412. return NULL;
  413. } elseif (!is_array($tip[$array_key])) {
  414. throw new fProgrammerException(
  415. '%1$s was called for the key, %2$s, which is not an array',
  416. __CLASS__ . '::remove()',
  417. $original_key
  418. );
  419. }
  420. $tip =& $tip[$array_key];
  421. }
  422. $key = end($array_keys);
  423. }
  424. if (!isset($tip[$key])) {
  425. return NULL;
  426. } elseif (!is_array($tip[$key])) {
  427. throw new fProgrammerException(
  428. '%1$s was called for the key, %2$s, which is not an array',
  429. __CLASS__ . '::remove()',
  430. $key
  431. );
  432. }
  433. if ($beginning) {
  434. return array_shift($tip[$key]);
  435. }
  436. return array_pop($tip[$key]);
  437. }
  438. /**
  439. * Resets the configuration of the class
  440. *
  441. * @internal
  442. *
  443. * @return void
  444. */
  445. static public function reset()
  446. {
  447. self::$normal_timespan = NULL;
  448. self::$persistent_timespan = NULL;
  449. self::$regenerated = FALSE;
  450. self::destroy();
  451. self::close();
  452. }
  453. /**
  454. * Sets data to the `$_SESSION` superglobal
  455. *
  456. * @param string $key The name to save the value under - array elements can be modified via `[sub-key]` syntax, and thus `[` and `]` can not be used in key names
  457. * @param mixed $value The value to store
  458. * @return void
  459. */
  460. static public function set($key, $value)
  461. {
  462. self::open();
  463. $tip =& $_SESSION;
  464. if ($bracket_pos = strpos($key, '[')) {
  465. $array_dereference = substr($key, $bracket_pos);
  466. $key = substr($key, 0, $bracket_pos);
  467. preg_match_all('#(?<=\[)[^\[\]]+(?=\])#', $array_dereference, $array_keys, PREG_SET_ORDER);
  468. $array_keys = array_map('current', $array_keys);
  469. array_unshift($array_keys, $key);
  470. foreach (array_slice($array_keys, 0, -1) as $array_key) {
  471. if (!isset($tip[$array_key]) || !is_array($tip[$array_key])) {
  472. $tip[$array_key] = array();
  473. }
  474. $tip =& $tip[$array_key];
  475. }
  476. $tip[end($array_keys)] = $value;
  477. } else {
  478. $tip[$key] = $value;
  479. }
  480. }
  481. /**
  482. * Sets the minimum length of a session - PHP might not clean up the session data right away once this timespan has elapsed
  483. *
  484. * Please be sure to set a custom session path via ::setPath() to ensure
  485. * another site on the server does not garbage collect the session files
  486. * from this site!
  487. *
  488. * Both of the timespan can accept either a integer timespan in seconds,
  489. * or an english description of a timespan (e.g. `'30 minutes'`, `'1 hour'`,
  490. * `'1 day 2 hours'`).
  491. *
  492. * @param string|integer $normal_timespan The normal, session-based cookie, length for the session
  493. * @param string|integer $persistent_timespan The persistent, timed-based cookie, length for the session - this is enabled by calling ::enabledPersistence() during login
  494. * @return void
  495. */
  496. static public function setLength($normal_timespan, $persistent_timespan=NULL)
  497. {
  498. if (self::$open || isset($_SESSION)) {
  499. throw new fProgrammerException(
  500. '%1$s must be called before any of %2$s, %3$s, %4$s, %5$s, %6$s, %7$s or %8$s',
  501. __CLASS__ . '::setLength()',
  502. __CLASS__ . '::add()',
  503. __CLASS__ . '::clear()',
  504. __CLASS__ . '::enablePersistence()',
  505. __CLASS__ . '::get()',
  506. __CLASS__ . '::open()',
  507. __CLASS__ . '::set()',
  508. 'session_start()'
  509. );
  510. }
  511. $seconds = (!is_numeric($normal_timespan)) ? strtotime($normal_timespan) - time() : $normal_timespan;
  512. self::$normal_timespan = $seconds;
  513. if ($persistent_timespan) {
  514. $seconds = (!is_numeric($persistent_timespan)) ? strtotime($persistent_timespan) - time() : $persistent_timespan;
  515. self::$persistent_timespan = $seconds;
  516. }
  517. ini_set('session.gc_maxlifetime', $seconds);
  518. }
  519. /**
  520. * Sets the path to store session files in
  521. *
  522. * This method should always be called with a non-standard directory
  523. * whenever ::setLength() is called to ensure that another site on the
  524. * server does not garbage collect the session files for this site.
  525. *
  526. * Standard session directories usually include `/tmp` and `/var/tmp`.
  527. *
  528. * @param string|fDirectory $directory The directory to store session files in
  529. * @return void
  530. */
  531. static public function setPath($directory)
  532. {
  533. if (self::$open || isset($_SESSION)) {
  534. throw new fProgrammerException(
  535. '%1$s must be called before any of %2$s, %3$s, %4$s, %5$s, %6$s, %7$s or %8$s',
  536. __CLASS__ . '::setPath()',
  537. __CLASS__ . '::add()',
  538. __CLASS__ . '::clear()',
  539. __CLASS__ . '::enablePersistence()',
  540. __CLASS__ . '::get()',
  541. __CLASS__ . '::open()',
  542. __CLASS__ . '::set()',
  543. 'session_start()'
  544. );
  545. }
  546. if (!$directory instanceof fDirectory) {
  547. $directory = new fDirectory($directory);
  548. }
  549. if (!$directory->isWritable()) {
  550. throw new fEnvironmentException(
  551. 'The directory specified, %s, is not writable',
  552. $directory->getPath()
  553. );
  554. }
  555. session_save_path($directory->getPath());
  556. }
  557. /**
  558. * Forces use as a static class
  559. *
  560. * @return fSession
  561. */
  562. private function __construct() { }
  563. }
  564. /**
  565. * Copyright (c) 2007-2011 Will Bond <will@flourishlib.com>, others
  566. *
  567. * Permission is hereby granted, free of charge, to any person obtaining a copy
  568. * of this software and associated documentation files (the "Software"), to deal
  569. * in the Software without restriction, including without limitation the rights
  570. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  571. * copies of the Software, and to permit persons to whom the Software is
  572. * furnished to do so, subject to the following conditions:
  573. *
  574. * The above copyright notice and this permission notice shall be included in
  575. * all copies or substantial portions of the Software.
  576. *
  577. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  578. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  579. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  580. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  581. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  582. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  583. * THE SOFTWARE.
  584. */