/src/Guzzle/Http/CookieJar/ArrayCookieJar.php

https://github.com/azogheb/guzzle · PHP · 252 lines · 120 code · 32 blank · 100 comment · 37 complexity · 3b6e84e9bb1a4c693e2c90a6771a2f85 MD5 · raw file

  1. <?php
  2. namespace Guzzle\Http\CookieJar;
  3. use Guzzle\Common\Collection;
  4. /**
  5. * Cookie jar that stores cookies an an array
  6. *
  7. * @author Michael Dowling <michael@guzzlephp.org>
  8. */
  9. class ArrayCookieJar implements CookieJarInterface
  10. {
  11. /**
  12. * @var array Loaded cookie data
  13. */
  14. protected $cookies = array();
  15. /**
  16. * Clear cookies currently held in the Cookie jar.
  17. *
  18. * Invoking this method without arguments will empty the whole Cookie
  19. * jar. If given a $domain argument only cookies belonging to that
  20. * domain will be removed. If given a $domain and $path argument, cookies
  21. * belonging to the specified path within that domain are removed. If given
  22. * all three arguments, then the cookie with the specified name, path and
  23. * domain is removed.
  24. *
  25. * @param string $domain (optional) Set to clear only cookies matching a domain
  26. * @param string $path (optional) Set to clear only cookies matching a domain and path
  27. * @param string $name (optional) Set to clear only cookies matching a domain, path, and name
  28. *
  29. * @return int Returns the number of deleted cookies
  30. */
  31. public function clear($domain = null, $path = null, $name = null)
  32. {
  33. $cookies = $this->getCookies($domain, $path, $name, false, false);
  34. $total = 0;
  35. foreach ($this->cookies as $i => $cookie) {
  36. foreach ($cookies as $c) {
  37. if ($c === $cookie) {
  38. $total++;
  39. unset($this->cookies[$i]);
  40. }
  41. }
  42. }
  43. return $total;
  44. }
  45. /**
  46. * Discard all temporary cookies.
  47. *
  48. * Scans for all cookies in the jar with either no expire field or a
  49. * true discard flag. To be called when the user agent shuts down according
  50. * to RFC 2965.
  51. *
  52. * @return int Returns the number of deleted cookies
  53. */
  54. public function clearTemporary()
  55. {
  56. $ctime = time();
  57. return $this->prune(function($cookie) use ($ctime) {
  58. return (!$cookie['discard'] && $cookie['expires']);
  59. });
  60. }
  61. /**
  62. * Delete any expired cookies
  63. *
  64. * @return int Returns the number of deleted cookies
  65. */
  66. public function deleteExpired()
  67. {
  68. $ctime = time();
  69. return $this->prune(function($cookie) use ($ctime) {
  70. return (!$cookie['expires'] || $ctime < $cookie['expires']);
  71. });
  72. }
  73. /**
  74. * Get all of the matching cookies
  75. *
  76. * @param string $domain (optional) Domain of the cookie
  77. * @param string $path (optional) Path of the cookie
  78. * @param string $name (optional) Name of the cookie
  79. * @param bool $skipDiscardables (optional) Set to TRUE to skip cookies with
  80. * the Discard attribute.
  81. * @param bool $skipExpired (optional) Set to FALSE to include expired
  82. *
  83. * @return array Returns an array of arrays. Each array contains the
  84. * following keys:
  85. *
  86. * domain (string) - Domain of the cookie
  87. * path (string) - Path of the cookie
  88. * cookie (array) - Array of cookie name, value (e.g. array('name', '123')
  89. * max_age (int) - Lifetime of the cookie in seconds
  90. * expires (int) - The UNIX timestamp when the cookie expires
  91. * version (int) - Version of the cookie specification. RFC 2965 is 1
  92. * secure (bool) - Whether or not this is a secure cookie
  93. * discard (bool) - Whether or not this is a discardable cookie
  94. * comment (string) - How the cookie is intended to be used
  95. * comment_url (str)- URL with info on how it will be used
  96. * port (string) - CSV list of ports
  97. * http_only (bool) - HTTP only cookie
  98. */
  99. public function getCookies($domain = null, $path = null, $name = null, $skipDiscardable = false, $skipExpired = true)
  100. {
  101. $ctime = time();
  102. $ret = array_values(array_filter($this->cookies, function($cookie) use ($domain, $path, $name, $skipDiscardable, $skipExpired, $ctime) {
  103. // Make sure the cookie is not expired
  104. if ($skipExpired && $cookie['expires'] && $ctime > $cookie['expires']) {
  105. return false;
  106. }
  107. $domain = strtolower($domain);
  108. $cookie['domain'] = strtolower($cookie['domain']);
  109. // Normalize the domain value
  110. $domainMatch = false;
  111. if ($domain && $cookie['domain']) {
  112. if ($domain == $cookie['domain']) {
  113. $domainMatch = true;
  114. } else if ($cookie['domain'][0] == '.') {
  115. $domainMatch = preg_match('/' . preg_quote($cookie['domain']) . '$/', $domain);
  116. }
  117. }
  118. // Check cookie name matches
  119. $nameMatch = $name && $cookie['cookie'][0] == $name;
  120. if (!$domain || $domainMatch) {
  121. if (!$path || !strcasecmp($path, $cookie['path']) || 0 === stripos($path, $cookie['path'])) {
  122. if (!$name || $nameMatch) {
  123. if (!$skipDiscardable || !$cookie['discard']) {
  124. return true;
  125. }
  126. }
  127. }
  128. }
  129. return false;
  130. }));
  131. return $ret;
  132. }
  133. /**
  134. * Save a cookie
  135. *
  136. * @parm array $cookieData Cookie information, including the following:
  137. * domain (string, required) - Domain of the cookie
  138. * path (string, required) - Path of the cookie
  139. * cookie - Array of cookie name (0) and value (1)
  140. * max_age (int, required) - Lifetime of the cookie in seconds
  141. * version (int) - Version of the cookie specification. Default is 0. RFC 2965 is 1
  142. * secure (bool) - If it is a secure cookie
  143. * discard (bool) - If it is a discardable cookie
  144. * comment (string) - How the cookie is intended to be used
  145. * comment_url (str) - URL with info on how it will be used
  146. * port (string) - CSV list of ports
  147. * http_only (bool) - HTTP only cookie
  148. *
  149. * @return ArrayCookieJar
  150. */
  151. public function save(array $cookieData)
  152. {
  153. if (!isset($cookieData['domain'])) {
  154. throw new \InvalidArgumentException('Cookies require a domain');
  155. }
  156. if (!isset($cookieData['cookie']) || !is_array($cookieData['cookie'])) {
  157. throw new \InvalidArgumentException('Cookies require a names and values');
  158. }
  159. // Extract the expires value and turn it into a UNIX timestamp if needed
  160. $cookieData['expires'] = isset($cookieData['expires']) ? (is_numeric($cookieData['expires']) ? $cookieData['expires'] : strtotime($cookieData['expires'])) : null;
  161. $cookieData['path'] = isset($cookieData['path']) ? $cookieData['path'] : '/';
  162. $cookieData['max_age'] = isset($cookieData['max_age']) ? $cookieData['max_age'] : 0;
  163. $cookieData['comment'] = isset($cookieData['comment']) ? $cookieData['comment'] : null;
  164. $cookieData['comment_url'] = isset($cookieData['comment_url']) ? $cookieData['comment_url'] : null;
  165. $cookieData['port'] = isset($cookieData['port']) ? $cookieData['port'] : array();
  166. $cookieData['version'] = isset($cookieData['version']) ? $cookieData['version'] : null;
  167. $cookieData['secure'] = array_key_exists('secure', $cookieData) ? $cookieData['secure'] : null;
  168. $cookieData['discard'] = array_key_exists('discard', $cookieData) ? $cookieData['discard'] : null;
  169. $cookieData['http_only'] = array_key_exists('http_only', $cookieData) ? $cookieData['http_only'] : false;
  170. // Calculate the expires date
  171. if (!$cookieData['expires'] && $cookieData['max_age']) {
  172. $cookieData['expires'] = time() + (int) $cookieData['max_age'];
  173. }
  174. $alreadyPresent = false;
  175. $keys = array('path', 'max_age', 'domain', 'http_only', 'port', 'secure');
  176. foreach ($this->cookies as $i => $cookie) {
  177. // Check the regular comparison fields
  178. foreach ($keys as $k) {
  179. if ($cookie[$k] != $cookieData[$k]) {
  180. continue 2;
  181. }
  182. }
  183. // Is it a different cookie value name?
  184. if ($cookie['cookie'][0] != $cookieData['cookie'][0]) {
  185. continue;
  186. }
  187. // The previously set cookie is a discard cookie and this one is not
  188. // so allow the new cookie to be set
  189. if (!$cookieData['discard'] && $cookie['discard']) {
  190. unset($this->cookies[$i]);
  191. continue;
  192. }
  193. // If the new cookie's expiration is further into the future, then
  194. // replace the old cookie
  195. if ($cookieData['expires'] > $cookie['expires']) {
  196. unset($this->cookies[$i]);
  197. continue;
  198. }
  199. $alreadyPresent = true;
  200. break;
  201. }
  202. if (!$alreadyPresent) {
  203. $this->cookies[] = $cookieData;
  204. }
  205. return $this;
  206. }
  207. /**
  208. * Prune the cookies using a callback function
  209. *
  210. * @param Closure $callback Callback function
  211. *
  212. * @return int Returns the number of removed cookies
  213. */
  214. protected function prune(\Closure $callback)
  215. {
  216. $originalCount = count($this->cookies);
  217. $this->cookies = array_filter($this->cookies, $callback);
  218. return $originalCount - count($this->cookies);
  219. }
  220. }