cookies.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380
  1. <?php
  2. /**
  3. * Base include file for SimpleTest
  4. * @package SimpleTest
  5. * @subpackage WebTester
  6. * @version $Id: cookies.php 2011 2011-04-29 08:22:48Z pp11 $
  7. */
  8. /**#@+
  9. * include other SimpleTest class files
  10. */
  11. require_once(dirname(__FILE__) . '/url.php');
  12. /**#@-*/
  13. /**
  14. * Cookie data holder. Cookie rules are full of pretty
  15. * arbitary stuff. I have used...
  16. * http://wp.netscape.com/newsref/std/cookie_spec.html
  17. * http://www.cookiecentral.com/faq/
  18. * @package SimpleTest
  19. * @subpackage WebTester
  20. */
  21. class SimpleCookie {
  22. private $host;
  23. private $name;
  24. private $value;
  25. private $path;
  26. private $expiry;
  27. private $is_secure;
  28. /**
  29. * Constructor. Sets the stored values.
  30. * @param string $name Cookie key.
  31. * @param string $value Value of cookie.
  32. * @param string $path Cookie path if not host wide.
  33. * @param string $expiry Expiry date as string.
  34. * @param boolean $is_secure Currently ignored.
  35. */
  36. function __construct($name, $value = false, $path = false, $expiry = false, $is_secure = false) {
  37. $this->host = false;
  38. $this->name = $name;
  39. $this->value = $value;
  40. $this->path = ($path ? $this->fixPath($path) : "/");
  41. $this->expiry = false;
  42. if (is_string($expiry)) {
  43. $this->expiry = strtotime($expiry);
  44. } elseif (is_integer($expiry)) {
  45. $this->expiry = $expiry;
  46. }
  47. $this->is_secure = $is_secure;
  48. }
  49. /**
  50. * Sets the host. The cookie rules determine
  51. * that the first two parts are taken for
  52. * certain TLDs and three for others. If the
  53. * new host does not match these rules then the
  54. * call will fail.
  55. * @param string $host New hostname.
  56. * @return boolean True if hostname is valid.
  57. * @access public
  58. */
  59. function setHost($host) {
  60. if ($host = $this->truncateHost($host)) {
  61. $this->host = $host;
  62. return true;
  63. }
  64. return false;
  65. }
  66. /**
  67. * Accessor for the truncated host to which this
  68. * cookie applies.
  69. * @return string Truncated hostname.
  70. * @access public
  71. */
  72. function getHost() {
  73. return $this->host;
  74. }
  75. /**
  76. * Test for a cookie being valid for a host name.
  77. * @param string $host Host to test against.
  78. * @return boolean True if the cookie would be valid
  79. * here.
  80. */
  81. function isValidHost($host) {
  82. return ($this->truncateHost($host) === $this->getHost());
  83. }
  84. /**
  85. * Extracts just the domain part that determines a
  86. * cookie's host validity.
  87. * @param string $host Host name to truncate.
  88. * @return string Domain or false on a bad host.
  89. * @access private
  90. */
  91. protected function truncateHost($host) {
  92. $tlds = SimpleUrl::getAllTopLevelDomains();
  93. if (preg_match('/[a-z\-]+\.(' . $tlds . ')$/i', $host, $matches)) {
  94. return $matches[0];
  95. } elseif (preg_match('/[a-z\-]+\.[a-z\-]+\.[a-z\-]+$/i', $host, $matches)) {
  96. return $matches[0];
  97. }
  98. return false;
  99. }
  100. /**
  101. * Accessor for name.
  102. * @return string Cookie key.
  103. * @access public
  104. */
  105. function getName() {
  106. return $this->name;
  107. }
  108. /**
  109. * Accessor for value. A deleted cookie will
  110. * have an empty string for this.
  111. * @return string Cookie value.
  112. * @access public
  113. */
  114. function getValue() {
  115. return $this->value;
  116. }
  117. /**
  118. * Accessor for path.
  119. * @return string Valid cookie path.
  120. * @access public
  121. */
  122. function getPath() {
  123. return $this->path;
  124. }
  125. /**
  126. * Tests a path to see if the cookie applies
  127. * there. The test path must be longer or
  128. * equal to the cookie path.
  129. * @param string $path Path to test against.
  130. * @return boolean True if cookie valid here.
  131. * @access public
  132. */
  133. function isValidPath($path) {
  134. return (strncmp(
  135. $this->fixPath($path),
  136. $this->getPath(),
  137. strlen($this->getPath())) == 0);
  138. }
  139. /**
  140. * Accessor for expiry.
  141. * @return string Expiry string.
  142. * @access public
  143. */
  144. function getExpiry() {
  145. if (! $this->expiry) {
  146. return false;
  147. }
  148. return gmdate("D, d M Y H:i:s", $this->expiry) . " GMT";
  149. }
  150. /**
  151. * Test to see if cookie is expired against
  152. * the cookie format time or timestamp.
  153. * Will give true for a session cookie.
  154. * @param integer/string $now Time to test against. Result
  155. * will be false if this time
  156. * is later than the cookie expiry.
  157. * Can be either a timestamp integer
  158. * or a cookie format date.
  159. * @access public
  160. */
  161. function isExpired($now) {
  162. if (! $this->expiry) {
  163. return true;
  164. }
  165. if (is_string($now)) {
  166. $now = strtotime($now);
  167. }
  168. return ($this->expiry < $now);
  169. }
  170. /**
  171. * Ages the cookie by the specified number of
  172. * seconds.
  173. * @param integer $interval In seconds.
  174. * @public
  175. */
  176. function agePrematurely($interval) {
  177. if ($this->expiry) {
  178. $this->expiry -= $interval;
  179. }
  180. }
  181. /**
  182. * Accessor for the secure flag.
  183. * @return boolean True if cookie needs SSL.
  184. * @access public
  185. */
  186. function isSecure() {
  187. return $this->is_secure;
  188. }
  189. /**
  190. * Adds a trailing and leading slash to the path
  191. * if missing.
  192. * @param string $path Path to fix.
  193. * @access private
  194. */
  195. protected function fixPath($path) {
  196. if (substr($path, 0, 1) != '/') {
  197. $path = '/' . $path;
  198. }
  199. if (substr($path, -1, 1) != '/') {
  200. $path .= '/';
  201. }
  202. return $path;
  203. }
  204. }
  205. /**
  206. * Repository for cookies. This stuff is a
  207. * tiny bit browser dependent.
  208. * @package SimpleTest
  209. * @subpackage WebTester
  210. */
  211. class SimpleCookieJar {
  212. private $cookies;
  213. /**
  214. * Constructor. Jar starts empty.
  215. * @access public
  216. */
  217. function __construct() {
  218. $this->cookies = array();
  219. }
  220. /**
  221. * Removes expired and temporary cookies as if
  222. * the browser was closed and re-opened.
  223. * @param string/integer $now Time to test expiry against.
  224. * @access public
  225. */
  226. function restartSession($date = false) {
  227. $surviving_cookies = array();
  228. for ($i = 0; $i < count($this->cookies); $i++) {
  229. if (! $this->cookies[$i]->getValue()) {
  230. continue;
  231. }
  232. if (! $this->cookies[$i]->getExpiry()) {
  233. continue;
  234. }
  235. if ($date && $this->cookies[$i]->isExpired($date)) {
  236. continue;
  237. }
  238. $surviving_cookies[] = $this->cookies[$i];
  239. }
  240. $this->cookies = $surviving_cookies;
  241. }
  242. /**
  243. * Ages all cookies in the cookie jar.
  244. * @param integer $interval The old session is moved
  245. * into the past by this number
  246. * of seconds. Cookies now over
  247. * age will be removed.
  248. * @access public
  249. */
  250. function agePrematurely($interval) {
  251. for ($i = 0; $i < count($this->cookies); $i++) {
  252. $this->cookies[$i]->agePrematurely($interval);
  253. }
  254. }
  255. /**
  256. * Sets an additional cookie. If a cookie has
  257. * the same name and path it is replaced.
  258. * @param string $name Cookie key.
  259. * @param string $value Value of cookie.
  260. * @param string $host Host upon which the cookie is valid.
  261. * @param string $path Cookie path if not host wide.
  262. * @param string $expiry Expiry date.
  263. * @access public
  264. */
  265. function setCookie($name, $value, $host = false, $path = '/', $expiry = false) {
  266. $cookie = new SimpleCookie($name, $value, $path, $expiry);
  267. if ($host) {
  268. $cookie->setHost($host);
  269. }
  270. $this->cookies[$this->findFirstMatch($cookie)] = $cookie;
  271. }
  272. /**
  273. * Finds a matching cookie to write over or the
  274. * first empty slot if none.
  275. * @param SimpleCookie $cookie Cookie to write into jar.
  276. * @return integer Available slot.
  277. * @access private
  278. */
  279. protected function findFirstMatch($cookie) {
  280. for ($i = 0; $i < count($this->cookies); $i++) {
  281. $is_match = $this->isMatch(
  282. $cookie,
  283. $this->cookies[$i]->getHost(),
  284. $this->cookies[$i]->getPath(),
  285. $this->cookies[$i]->getName());
  286. if ($is_match) {
  287. return $i;
  288. }
  289. }
  290. return count($this->cookies);
  291. }
  292. /**
  293. * Reads the most specific cookie value from the
  294. * browser cookies. Looks for the longest path that
  295. * matches.
  296. * @param string $host Host to search.
  297. * @param string $path Applicable path.
  298. * @param string $name Name of cookie to read.
  299. * @return string False if not present, else the
  300. * value as a string.
  301. * @access public
  302. */
  303. function getCookieValue($host, $path, $name) {
  304. $longest_path = '';
  305. foreach ($this->cookies as $cookie) {
  306. if ($this->isMatch($cookie, $host, $path, $name)) {
  307. if (strlen($cookie->getPath()) > strlen($longest_path)) {
  308. $value = $cookie->getValue();
  309. $longest_path = $cookie->getPath();
  310. }
  311. }
  312. }
  313. return (isset($value) ? $value : false);
  314. }
  315. /**
  316. * Tests cookie for matching against search
  317. * criteria.
  318. * @param SimpleTest $cookie Cookie to test.
  319. * @param string $host Host must match.
  320. * @param string $path Cookie path must be shorter than
  321. * this path.
  322. * @param string $name Name must match.
  323. * @return boolean True if matched.
  324. * @access private
  325. */
  326. protected function isMatch($cookie, $host, $path, $name) {
  327. if ($cookie->getName() != $name) {
  328. return false;
  329. }
  330. if ($host && $cookie->getHost() && ! $cookie->isValidHost($host)) {
  331. return false;
  332. }
  333. if (! $cookie->isValidPath($path)) {
  334. return false;
  335. }
  336. return true;
  337. }
  338. /**
  339. * Uses a URL to sift relevant cookies by host and
  340. * path. Results are list of strings of form "name=value".
  341. * @param SimpleUrl $url Url to select by.
  342. * @return array Valid name and value pairs.
  343. * @access public
  344. */
  345. function selectAsPairs($url) {
  346. $pairs = array();
  347. foreach ($this->cookies as $cookie) {
  348. if ($this->isMatch($cookie, $url->getHost(), $url->getPath(), $cookie->getName())) {
  349. $pairs[] = $cookie->getName() . '=' . $cookie->getValue();
  350. }
  351. }
  352. return $pairs;
  353. }
  354. }
  355. ?>