123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385 |
- <?php
- /**
- * Licensed to Jasig under one or more contributor license
- * agreements. See the NOTICE file distributed with this work for
- * additional information regarding copyright ownership.
- *
- * Jasig licenses this file to you under the Apache License,
- * Version 2.0 (the "License"); you may not use this file except in
- * compliance with the License. You may obtain a copy of the License at:
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * PHP Version 5
- *
- * @file CAS/CookieJar.php
- * @category Authentication
- * @package PhpCAS
- * @author Adam Franco <afranco@middlebury.edu>
- * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
- * @link https://wiki.jasig.org/display/CASC/phpCAS
- */
- /**
- * This class provides access to service cookies and handles parsing of response
- * headers to pull out cookie values.
- *
- * @class CAS_CookieJar
- * @category Authentication
- * @package PhpCAS
- * @author Adam Franco <afranco@middlebury.edu>
- * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
- * @link https://wiki.jasig.org/display/CASC/phpCAS
- */
- class CAS_CookieJar
- {
- private $_cookies;
- /**
- * Create a new cookie jar by passing it a reference to an array in which it
- * should store cookies.
- *
- * @param array &$storageArray Array to store cookies
- *
- * @return void
- */
- public function __construct (array &$storageArray)
- {
- $this->_cookies =& $storageArray;
- }
- /**
- * Store cookies for a web service request.
- * Cookie storage is based on RFC 2965: http://www.ietf.org/rfc/rfc2965.txt
- *
- * @param string $request_url The URL that generated the response headers.
- * @param array $response_headers An array of the HTTP response header strings.
- *
- * @return void
- *
- * @access private
- */
- public function storeCookies ($request_url, $response_headers)
- {
- $urlParts = parse_url($request_url);
- $defaultDomain = $urlParts['host'];
- $cookies = $this->parseCookieHeaders($response_headers, $defaultDomain);
- foreach ($cookies as $cookie) {
- // Enforce the same-origin policy by verifying that the cookie
- // would match the url that is setting it
- if (!$this->cookieMatchesTarget($cookie, $urlParts)) {
- continue;
- }
- // store the cookie
- $this->storeCookie($cookie);
- phpCAS::trace($cookie['name'].' -> '.$cookie['value']);
- }
- }
- /**
- * Retrieve cookies applicable for a web service request.
- * Cookie applicability is based on RFC 2965: http://www.ietf.org/rfc/rfc2965.txt
- *
- * @param string $request_url The url that the cookies will be for.
- *
- * @return array An array containing cookies. E.g. array('name' => 'val');
- *
- * @access private
- */
- public function getCookies ($request_url)
- {
- if (!count($this->_cookies)) {
- return array();
- }
- // If our request URL can't be parsed, no cookies apply.
- $target = parse_url($request_url);
- if ($target === false) {
- return array();
- }
- $this->expireCookies();
- $matching_cookies = array();
- foreach ($this->_cookies as $key => $cookie) {
- if ($this->cookieMatchesTarget($cookie, $target)) {
- $matching_cookies[$cookie['name']] = $cookie['value'];
- }
- }
- return $matching_cookies;
- }
- /**
- * Parse Cookies without PECL
- * From the comments in http://php.net/manual/en/function.http-parse-cookie.php
- *
- * @param array $header array of header lines.
- * @param string $defaultDomain The domain to use if none is specified in
- * the cookie.
- *
- * @return array of cookies
- */
- protected function parseCookieHeaders( $header, $defaultDomain )
- {
- phpCAS::traceBegin();
- $cookies = array();
- foreach ( $header as $line ) {
- if ( preg_match('/^Set-Cookie2?: /i', $line)) {
- $cookies[] = $this->parseCookieHeader($line, $defaultDomain);
- }
- }
- phpCAS::traceEnd($cookies);
- return $cookies;
- }
- /**
- * Parse a single cookie header line.
- *
- * Based on RFC2965 http://www.ietf.org/rfc/rfc2965.txt
- *
- * @param string $line The header line.
- * @param string $defaultDomain The domain to use if none is specified in
- * the cookie.
- *
- * @return array
- */
- protected function parseCookieHeader ($line, $defaultDomain)
- {
- if (!$defaultDomain) {
- throw new CAS_InvalidArgumentException(
- '$defaultDomain was not provided.'
- );
- }
- // Set our default values
- $cookie = array(
- 'domain' => $defaultDomain,
- 'path' => '/',
- 'secure' => false,
- );
- $line = preg_replace('/^Set-Cookie2?: /i', '', trim($line));
- // trim any trailing semicolons.
- $line = trim($line, ';');
- phpCAS::trace("Cookie Line: $line");
- // This implementation makes the assumption that semicolons will not
- // be present in quoted attribute values. While attribute values that
- // contain semicolons are allowed by RFC2965, they are hopefully rare
- // enough to ignore for our purposes. Most browsers make the same
- // assumption.
- $attributeStrings = explode(';', $line);
- foreach ( $attributeStrings as $attributeString ) {
- // split on the first equals sign and use the rest as value
- $attributeParts = explode('=', $attributeString, 2);
- $attributeName = trim($attributeParts[0]);
- $attributeNameLC = strtolower($attributeName);
- if (isset($attributeParts[1])) {
- $attributeValue = trim($attributeParts[1]);
- // Values may be quoted strings.
- if (strpos($attributeValue, '"') === 0) {
- $attributeValue = trim($attributeValue, '"');
- // unescape any escaped quotes:
- $attributeValue = str_replace('\"', '"', $attributeValue);
- }
- } else {
- $attributeValue = null;
- }
- switch ($attributeNameLC) {
- case 'expires':
- $cookie['expires'] = strtotime($attributeValue);
- break;
- case 'max-age':
- $cookie['max-age'] = (int)$attributeValue;
- // Set an expiry time based on the max-age
- if ($cookie['max-age']) {
- $cookie['expires'] = time() + $cookie['max-age'];
- } else {
- // If max-age is zero, then the cookie should be removed
- // imediately so set an expiry before now.
- $cookie['expires'] = time() - 1;
- }
- break;
- case 'secure':
- $cookie['secure'] = true;
- break;
- case 'domain':
- case 'path':
- case 'port':
- case 'version':
- case 'comment':
- case 'commenturl':
- case 'discard':
- case 'httponly':
- $cookie[$attributeNameLC] = $attributeValue;
- break;
- default:
- $cookie['name'] = $attributeName;
- $cookie['value'] = $attributeValue;
- }
- }
- return $cookie;
- }
- /**
- * Add, update, or remove a cookie.
- *
- * @param array $cookie A cookie array as created by parseCookieHeaders()
- *
- * @return void
- *
- * @access protected
- */
- protected function storeCookie ($cookie)
- {
- // Discard any old versions of this cookie.
- $this->discardCookie($cookie);
- $this->_cookies[] = $cookie;
- }
- /**
- * Discard an existing cookie
- *
- * @param array $cookie An cookie
- *
- * @return void
- *
- * @access protected
- */
- protected function discardCookie ($cookie)
- {
- if (!isset($cookie['domain'])
- || !isset($cookie['path'])
- || !isset($cookie['path'])
- ) {
- throw new CAS_InvalidArgumentException('Invalid Cookie array passed.');
- }
- foreach ($this->_cookies as $key => $old_cookie) {
- if ( $cookie['domain'] == $old_cookie['domain']
- && $cookie['path'] == $old_cookie['path']
- && $cookie['name'] == $old_cookie['name']
- ) {
- unset($this->_cookies[$key]);
- }
- }
- }
- /**
- * Go through our stored cookies and remove any that are expired.
- *
- * @return void
- *
- * @access protected
- */
- protected function expireCookies ()
- {
- foreach ($this->_cookies as $key => $cookie) {
- if (isset($cookie['expires']) && $cookie['expires'] < time()) {
- unset($this->_cookies[$key]);
- }
- }
- }
- /**
- * Answer true if cookie is applicable to a target.
- *
- * @param array $cookie An array of cookie attributes.
- * @param array|false $target An array of URL attributes as generated by parse_url().
- *
- * @return bool
- *
- * @access private
- */
- protected function cookieMatchesTarget ($cookie, $target)
- {
- if (!is_array($target)) {
- throw new CAS_InvalidArgumentException(
- '$target must be an array of URL attributes as generated by parse_url().'
- );
- }
- if (!isset($target['host'])) {
- throw new CAS_InvalidArgumentException(
- '$target must be an array of URL attributes as generated by parse_url().'
- );
- }
- // Verify that the scheme matches
- if ($cookie['secure'] && $target['scheme'] != 'https') {
- return false;
- }
- // Verify that the host matches
- // Match domain and mulit-host cookies
- if (strpos($cookie['domain'], '.') === 0) {
- // .host.domain.edu cookies are valid for host.domain.edu
- if (substr($cookie['domain'], 1) == $target['host']) {
- // continue with other checks
- } else {
- // non-exact host-name matches.
- // check that the target host a.b.c.edu is within .b.c.edu
- $pos = strripos($target['host'], $cookie['domain']);
- if (!$pos) {
- return false;
- }
- // verify that the cookie domain is the last part of the host.
- if ($pos + strlen($cookie['domain']) != strlen($target['host'])) {
- return false;
- }
- // verify that the host name does not contain interior dots as per
- // RFC 2965 section 3.3.2 Rejecting Cookies
- // http://www.ietf.org/rfc/rfc2965.txt
- $hostname = substr($target['host'], 0, $pos);
- if (strpos($hostname, '.') !== false) {
- return false;
- }
- }
- } else {
- // If the cookie host doesn't begin with '.',
- // the host must case-insensitive match exactly
- if (strcasecmp($target['host'], $cookie['domain']) !== 0) {
- return false;
- }
- }
- // Verify that the port matches
- if (isset($cookie['ports'])
- && !in_array($target['port'], $cookie['ports'])
- ) {
- return false;
- }
- // Verify that the path matches
- if (strpos($target['path'], $cookie['path']) !== 0) {
- return false;
- }
- return true;
- }
- }
- ?>
|