phpCAS  version 1.4.0
CookieJar.php
Go to the documentation of this file.
1 <?php
2 
42 {
43 
44  private $_cookies;
45 
54  public function __construct (array &$storageArray)
55  {
56  $this->_cookies =& $storageArray;
57  }
58 
70  public function storeCookies ($request_url, $response_headers)
71  {
72  $urlParts = parse_url($request_url);
73  $defaultDomain = $urlParts['host'];
74 
75  $cookies = $this->parseCookieHeaders($response_headers, $defaultDomain);
76 
77  foreach ($cookies as $cookie) {
78  // Enforce the same-origin policy by verifying that the cookie
79  // would match the url that is setting it
80  if (!$this->cookieMatchesTarget($cookie, $urlParts)) {
81  continue;
82  }
83 
84  // store the cookie
85  $this->storeCookie($cookie);
86 
87  phpCAS::trace($cookie['name'].' -> '.$cookie['value']);
88  }
89  }
90 
101  public function getCookies ($request_url)
102  {
103  if (!count($this->_cookies)) {
104  return array();
105  }
106 
107  // If our request URL can't be parsed, no cookies apply.
108  $target = parse_url($request_url);
109  if ($target === false) {
110  return array();
111  }
112 
113  $this->expireCookies();
114 
115  $matching_cookies = array();
116  foreach ($this->_cookies as $key => $cookie) {
117  if ($this->cookieMatchesTarget($cookie, $target)) {
118  $matching_cookies[$cookie['name']] = $cookie['value'];
119  }
120  }
121  return $matching_cookies;
122  }
123 
124 
135  protected function parseCookieHeaders( $header, $defaultDomain )
136  {
138  $cookies = array();
139  foreach ( $header as $line ) {
140  if ( preg_match('/^Set-Cookie2?: /i', $line)) {
141  $cookies[] = $this->parseCookieHeader($line, $defaultDomain);
142  }
143  }
144 
145  phpCAS::traceEnd($cookies);
146  return $cookies;
147  }
148 
160  protected function parseCookieHeader ($line, $defaultDomain)
161  {
162  if (!$defaultDomain) {
164  '$defaultDomain was not provided.'
165  );
166  }
167 
168  // Set our default values
169  $cookie = array(
170  'domain' => $defaultDomain,
171  'path' => '/',
172  'secure' => false,
173  );
174 
175  $line = preg_replace('/^Set-Cookie2?: /i', '', trim($line));
176 
177  // trim any trailing semicolons.
178  $line = trim($line, ';');
179 
180  phpCAS::trace("Cookie Line: $line");
181 
182  // This implementation makes the assumption that semicolons will not
183  // be present in quoted attribute values. While attribute values that
184  // contain semicolons are allowed by RFC2965, they are hopefully rare
185  // enough to ignore for our purposes. Most browsers make the same
186  // assumption.
187  $attributeStrings = explode(';', $line);
188 
189  foreach ( $attributeStrings as $attributeString ) {
190  // split on the first equals sign and use the rest as value
191  $attributeParts = explode('=', $attributeString, 2);
192 
193  $attributeName = trim($attributeParts[0]);
194  $attributeNameLC = strtolower($attributeName);
195 
196  if (isset($attributeParts[1])) {
197  $attributeValue = trim($attributeParts[1]);
198  // Values may be quoted strings.
199  if (strpos($attributeValue, '"') === 0) {
200  $attributeValue = trim($attributeValue, '"');
201  // unescape any escaped quotes:
202  $attributeValue = str_replace('\"', '"', $attributeValue);
203  }
204  } else {
205  $attributeValue = null;
206  }
207 
208  switch ($attributeNameLC) {
209  case 'expires':
210  $cookie['expires'] = strtotime($attributeValue);
211  break;
212  case 'max-age':
213  $cookie['max-age'] = (int)$attributeValue;
214  // Set an expiry time based on the max-age
215  if ($cookie['max-age']) {
216  $cookie['expires'] = time() + $cookie['max-age'];
217  } else {
218  // If max-age is zero, then the cookie should be removed
219  // imediately so set an expiry before now.
220  $cookie['expires'] = time() - 1;
221  }
222  break;
223  case 'secure':
224  $cookie['secure'] = true;
225  break;
226  case 'domain':
227  case 'path':
228  case 'port':
229  case 'version':
230  case 'comment':
231  case 'commenturl':
232  case 'discard':
233  case 'httponly':
234  case 'samesite':
235  $cookie[$attributeNameLC] = $attributeValue;
236  break;
237  default:
238  $cookie['name'] = $attributeName;
239  $cookie['value'] = $attributeValue;
240  }
241  }
242 
243  return $cookie;
244  }
245 
255  protected function storeCookie ($cookie)
256  {
257  // Discard any old versions of this cookie.
258  $this->discardCookie($cookie);
259  $this->_cookies[] = $cookie;
260 
261  }
262 
272  protected function discardCookie ($cookie)
273  {
274  if (!isset($cookie['domain'])
275  || !isset($cookie['path'])
276  || !isset($cookie['path'])
277  ) {
278  throw new CAS_InvalidArgumentException('Invalid Cookie array passed.');
279  }
280 
281  foreach ($this->_cookies as $key => $old_cookie) {
282  if ( $cookie['domain'] == $old_cookie['domain']
283  && $cookie['path'] == $old_cookie['path']
284  && $cookie['name'] == $old_cookie['name']
285  ) {
286  unset($this->_cookies[$key]);
287  }
288  }
289  }
290 
298  protected function expireCookies ()
299  {
300  foreach ($this->_cookies as $key => $cookie) {
301  if (isset($cookie['expires']) && $cookie['expires'] < time()) {
302  unset($this->_cookies[$key]);
303  }
304  }
305  }
306 
317  protected function cookieMatchesTarget ($cookie, $target)
318  {
319  if (!is_array($target)) {
321  '$target must be an array of URL attributes as generated by parse_url().'
322  );
323  }
324  if (!isset($target['host'])) {
326  '$target must be an array of URL attributes as generated by parse_url().'
327  );
328  }
329 
330  // Verify that the scheme matches
331  if ($cookie['secure'] && $target['scheme'] != 'https') {
332  return false;
333  }
334 
335  // Verify that the host matches
336  // Match domain and mulit-host cookies
337  if (strpos($cookie['domain'], '.') === 0) {
338  // .host.domain.edu cookies are valid for host.domain.edu
339  if (substr($cookie['domain'], 1) == $target['host']) {
340  // continue with other checks
341  } else {
342  // non-exact host-name matches.
343  // check that the target host a.b.c.edu is within .b.c.edu
344  $pos = strripos($target['host'], $cookie['domain']);
345  if (!$pos) {
346  return false;
347  }
348  // verify that the cookie domain is the last part of the host.
349  if ($pos + strlen($cookie['domain']) != strlen($target['host'])) {
350  return false;
351  }
352  // verify that the host name does not contain interior dots as per
353  // RFC 2965 section 3.3.2 Rejecting Cookies
354  // http://www.ietf.org/rfc/rfc2965.txt
355  $hostname = substr($target['host'], 0, $pos);
356  if (strpos($hostname, '.') !== false) {
357  return false;
358  }
359  }
360  } else {
361  // If the cookie host doesn't begin with '.',
362  // the host must case-insensitive match exactly
363  if (strcasecmp($target['host'], $cookie['domain']) !== 0) {
364  return false;
365  }
366  }
367 
368  // Verify that the port matches
369  if (isset($cookie['ports'])
370  && !in_array($target['port'], $cookie['ports'])
371  ) {
372  return false;
373  }
374 
375  // Verify that the path matches
376  if (strpos($target['path'], $cookie['path']) !== 0) {
377  return false;
378  }
379 
380  return true;
381  }
382 
383 }
384 
385 ?>
parseCookieHeader($line, $defaultDomain)
Definition: CookieJar.php:160
cookieMatchesTarget($cookie, $target)
Definition: CookieJar.php:317
static traceEnd($res='')
Definition: CAS.php:675
__construct(array &$storageArray)
Definition: CookieJar.php:54
static trace($str)
Definition: CAS.php:616
storeCookie($cookie)
Definition: CookieJar.php:255
getCookies($request_url)
Definition: CookieJar.php:101
parseCookieHeaders( $header, $defaultDomain)
Definition: CookieJar.php:135
storeCookies($request_url, $response_headers)
Definition: CookieJar.php:70
static traceBegin()
Definition: CAS.php:628
discardCookie($cookie)
Definition: CookieJar.php:272