code:
<?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/ProxiedService/Http/Abstract.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 implements common methods for ProxiedService implementations included
 * with phpCAS.
 *
 * @class    CAS_ProxiedService_Http_Abstract
 * @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
 */
abstract class CAS_ProxiedService_Http_Abstract extends
CAS_ProxiedService_Abstract implements CAS_ProxiedService_Http
{
    /**
     * The HTTP request mechanism talking to the target service.
     *
     * @var CAS_Request_RequestInterface $requestHandler
     */
    protected $requestHandler;

    /**
     * The storage mechanism for cookies set by the target service.
     *
     * @var CAS_CookieJar $_cookieJar
     */
    private $_cookieJar;

    /**
     * Constructor.
     *
     * @param CAS_Request_RequestInterface $requestHandler request handler object
     * @param CAS_CookieJar                $cookieJar      cookieJar object
     *
     * @return void
     */
    public function __construct(CAS_Request_RequestInterface $requestHandler,
        CAS_CookieJar $cookieJar
    ) {
        $this->requestHandler = $requestHandler;
        $this->_cookieJar = $cookieJar;
    }

    /**
     * The target service url.
     * @var string $_url;
     */
    private $_url;

    /**
     * Answer a service identifier (URL) for whom we should fetch a proxy ticket.
     *
     * @return string
     * @throws Exception If no service url is available.
     */
    public function getServiceUrl()
    {
        if (empty($this->_url)) {
            throw new CAS_ProxiedService_Exception(
                'No URL set via ' . get_class($this) . '->setUrl($url).'
            );
        }

        return $this->_url;
    }

    /*********************************************************
     * Configure the Request
     *********************************************************/

    /**
     * Set the URL of the Request
     *
     * @param string $url url to set
     *
     * @return void
     * @throws CAS_OutOfSequenceException If called after the Request has been sent.
     */
    public function setUrl($url)
    {
        if ($this->hasBeenSent()) {
            throw new CAS_OutOfSequenceException(
                'Cannot set the URL, request already sent.'
            );
        }
        if (!is_string($url)) {
            throw new CAS_InvalidArgumentException('$url must be a string.');
        }

        $this->_url = $url;
    }

    /*********************************************************
     * 2. Send the Request
     *********************************************************/

    /**
     * Perform the request.
     *
     * @return void
     * @throws CAS_OutOfSequenceException If called multiple times.
     * @throws CAS_ProxyTicketException If there is a proxy-ticket failure.
     *		The code of the Exception will be one of:
     *			PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE
     *			PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE
     *			PHPCAS_SERVICE_PT_FAILURE
     * @throws CAS_ProxiedService_Exception If there is a failure sending the
     * request to the target service.
     */
    public function send()
    {
        if ($this->hasBeenSent()) {
            throw new CAS_OutOfSequenceException(
                'Cannot send, request already sent.'
            );
        }

        phpCAS::traceBegin();

        // Get our proxy ticket and append it to our URL.
        $this->initializeProxyTicket();
        $url = $this->getServiceUrl();
        if (strstr($url, '?') === false) {
            $url = $url . '?ticket=' . $this->getProxyTicket();
        } else {
            $url = $url . '&ticket=' . $this->getProxyTicket();
        }

        try {
            $this->makeRequest($url);
        } catch (Exception $e) {
            phpCAS::traceEnd();
            throw $e;
        }
    }

    /**
     * Indicator of the number of requests (including redirects performed.
     *
     * @var int $_numRequests;
     */
    private $_numRequests = 0;

    /**
     * The response headers.
     *
     * @var array $_responseHeaders;
     */
    private $_responseHeaders = array();

    /**
     * The response status code.
     *
     * @var int $_responseStatusCode;
     */
    private $_responseStatusCode = '';

    /**
     * The response headers.
     *
     * @var string $_responseBody;
     */
    private $_responseBody = '';

    /**
     * Build and perform a request, following redirects
     *
     * @param string $url url for the request
     *
     * @return void
     * @throws CAS_ProxyTicketException If there is a proxy-ticket failure.
     *		The code of the Exception will be one of:
     *			PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE
     *			PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE
     *			PHPCAS_SERVICE_PT_FAILURE
     * @throws CAS_ProxiedService_Exception If there is a failure sending the
     * request to the target service.
     */
    protected function makeRequest($url)
    {
        // Verify that we are not in a redirect loop
        $this->_numRequests++;
        if ($this->_numRequests > 4) {
            $message = 'Exceeded the maximum number of redirects (3) in proxied service request.';
            phpCAS::trace($message);
            throw new CAS_ProxiedService_Exception($message);
        }

        // Create a new request.
        $request = clone $this->requestHandler;
        $request->setUrl($url);

        // Add any cookies to the request.
        $request->addCookies($this->_cookieJar->getCookies($url));

        // Add any other parts of the request needed by concrete classes
        $this->populateRequest($request);

        // Perform the request.
        phpCAS::trace('Performing proxied service request to \'' . $url . '\'');
        if (!$request->send()) {
            $message = 'Could not perform proxied service request to URL`'
            . $url . '\'. ' . $request->getErrorMessage();
            phpCAS::trace($message);
            throw new CAS_ProxiedService_Exception($message);
        }

        // Store any cookies from the response;
        $this->_cookieJar->storeCookies($url, $request->getResponseHeaders());

        // Follow any redirects
        if ($redirectUrl = $this->getRedirectUrl($request->getResponseHeaders())
        ) {
            phpCAS::trace('Found redirect:' . $redirectUrl);
            $this->makeRequest($redirectUrl);
        } else {

            $this->_responseHeaders = $request->getResponseHeaders();
            $this->_responseBody = $request->getResponseBody();
            $this->_responseStatusCode = $request->getResponseStatusCode();
        }
    }

    /**
     * Add any other parts of the request needed by concrete classes
     *
     * @param CAS_Request_RequestInterface $request request interface object
     *
     * @return void
     */
    abstract protected function populateRequest(
        CAS_Request_RequestInterface $request
    );

    /**
     * Answer a redirect URL if a redirect header is found, otherwise null.
     *
     * @param array $responseHeaders response header to extract a redirect from
     *
     * @return string|null
     */
    protected function getRedirectUrl(array $responseHeaders)
    {
        // Check for the redirect after authentication
        foreach ($responseHeaders as $header) {
            if ( preg_match('/^(Location:|URI:)\s*([^\s]+.*)$/', $header, $matches)
            ) {
                return trim(array_pop($matches));
            }
        }
        return null;
    }

    /*********************************************************
     * 3. Access the response
     *********************************************************/

    /**
     * Answer true if our request has been sent yet.
     *
     * @return bool
     */
    protected function hasBeenSent()
    {
        return ($this->_numRequests > 0);
    }

    /**
     * Answer the headers of the response.
     *
     * @return array An array of header strings.
     * @throws CAS_OutOfSequenceException If called before the Request has been sent.
     */
    public function getResponseHeaders()
    {
        if (!$this->hasBeenSent()) {
            throw new CAS_OutOfSequenceException(
                'Cannot access response, request not sent yet.'
            );
        }

        return $this->_responseHeaders;
    }

    /**
     * Answer HTTP status code of the response
     *
     * @return int
     * @throws CAS_OutOfSequenceException If called before the Request has been sent.
     */
    public function getResponseStatusCode()
    {
        if (!$this->hasBeenSent()) {
            throw new CAS_OutOfSequenceException(
                'Cannot access response, request not sent yet.'
            );
        }

        return $this->_responseStatusCode;
    }

    /**
     * Answer the body of response.
     *
     * @return string
     * @throws CAS_OutOfSequenceException If called before the Request has been sent.
     */
    public function getResponseBody()
    {
        if (!$this->hasBeenSent()) {
            throw new CAS_OutOfSequenceException(
                'Cannot access response, request not sent yet.'
            );
        }

        return $this->_responseBody;
    }

    /**
     * Answer the cookies from the response. This may include cookies set during
     * redirect responses.
     *
     * @return array An array containing cookies. E.g. array('name' => 'val');
     */
    public function getCookies()
    {
        return $this->_cookieJar->getCookies($this->getServiceUrl());
    }

}
?>


code:
<?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/ProxiedService/Http/Get.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 is used to make proxied service requests via the HTTP GET method.
 *
 * Usage Example:
 *
 *	try {
 *		$service = phpCAS::getProxiedService(PHPCAS_PROXIED_SERVICE_HTTP_GET);
 *		$service->setUrl('http://www.example.com/path/');
 *		$service->send();
 *		if ($service->getResponseStatusCode() == 200)
 *			return $service->getResponseBody();
 *		else
 *			// The service responded with an error code 404, 500, etc.
 *			throw new Exception('The service responded with an error.');
 *
 * 	} catch (CAS_ProxyTicketException $e) {
 *	    if ($e->getCode() == PHPCAS_SERVICE_PT_FAILURE)
 *			return "Your login has timed out. You need to log in again.";
 *		else
 *			// Other proxy ticket errors are from bad request format
 *          // (shouldn't happen) or CAS server failure (unlikely)
 *          // so lets just stop if we hit those.
 *			throw $e;
 *	} catch (CAS_ProxiedService_Exception $e) {
 *		// Something prevented the service request from being sent or received.
 *		// We didn't even get a valid error response (404, 500, etc), so this
 *		// might be caused by a network error or a DNS resolution failure.
 *		// We could handle it in some way, but for now we will just stop.
 *		throw $e;
 *	}
 *
 * @class    CAS_ProxiedService_Http_Get
 * @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_ProxiedService_Http_Get
extends CAS_ProxiedService_Http_Abstract
{

    /**
     * Add any other parts of the request needed by concrete classes
     *
     * @param CAS_Request_RequestInterface $request request interface
     *
     * @return void
     */
    protected function populateRequest (CAS_Request_RequestInterface $request)
    {
        // do nothing, since the URL has already been sent and that is our
        // only data.
    }
}
?>

license: mit


code:
<?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/ProxiedService/Http/Post.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 is used to make proxied service requests via the HTTP POST method.
 *
 * Usage Example:
 *
 *	try {
 * 		$service = phpCAS::getProxiedService(PHPCAS_PROXIED_SERVICE_HTTP_POST);
 * 		$service->setUrl('http://www.example.com/path/');
 *		$service->setContentType('text/xml');
 *		$service->setBody('<?xml version="1.0"?'.'><methodCall><methodName>example.search</methodName></methodCall>');
 * 		$service->send();
 *		if ($service->getResponseStatusCode() == 200)
 *			return $service->getResponseBody();
 *		else
 *			// The service responded with an error code 404, 500, etc.
 *			throw new Exception('The service responded with an error.');
 *
 *	} catch (CAS_ProxyTicketException $e) {
 *		if ($e->getCode() == PHPCAS_SERVICE_PT_FAILURE)
 *			return "Your login has timed out. You need to log in again.";
 *		else
 *			// Other proxy ticket errors are from bad request format
 *          // (shouldn't happen) or CAS server failure (unlikely) so lets just
 *          // stop if we hit those.
 *			throw $e;
 *	} catch (CAS_ProxiedService_Exception $e) {
 *		// Something prevented the service request from being sent or received.
 *		// We didn't even get a valid error response (404, 500, etc), so this
 *		// might be caused by a network error or a DNS resolution failure.
 *		// We could handle it in some way, but for now we will just stop.
 *		throw $e;
 *	}
 *
 * @class    CAS_ProxiedService_Http_Post
 * @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_ProxiedService_Http_Post
extends CAS_ProxiedService_Http_Abstract
{

    /**
     * The content-type of this request
     *
     * @var string $_contentType
     */
    private $_contentType;

    /**
     * The body of the this request
     *
     * @var string $_body
     */
    private $_body;

    /**
     * Set the content type of this POST request.
     *
     * @param string $contentType content type
     *
     * @return void
     * @throws CAS_OutOfSequenceException If called after the Request has been sent.
     */
    public function setContentType ($contentType)
    {
        if ($this->hasBeenSent()) {
            throw new CAS_OutOfSequenceException(
                'Cannot set the content type, request already sent.'
            );
        }

        $this->_contentType = $contentType;
    }

    /**
     * Set the body of this POST request.
     *
     * @param string $body body to set
     *
     * @return void
     * @throws CAS_OutOfSequenceException If called after the Request has been sent.
     */
    public function setBody ($body)
    {
        if ($this->hasBeenSent()) {
            throw new CAS_OutOfSequenceException(
                'Cannot set the body, request already sent.'
            );
        }

        $this->_body = $body;
    }

    /**
     * Add any other parts of the request needed by concrete classes
     *
     * @param CAS_Request_RequestInterface $request request interface class
     *
     * @return void
     */
    protected function populateRequest (CAS_Request_RequestInterface $request)
    {
        if (empty($this->_contentType) && !empty($this->_body)) {
            throw new CAS_ProxiedService_Exception(
                "If you pass a POST body, you must specify a content type via "
                .get_class($this).'->setContentType($contentType).'
            );
        }

        $request->makePost();
        if (!empty($this->_body)) {
            $request->addHeader('Content-Type: '.$this->_contentType);
            $request->addHeader('Content-Length: '.strlen($this->_body));
            $request->setPostBody($this->_body);
        }
    }

}
?>


code:
<?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/ProxiedService/Imap.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
 */

/**
 * Provides access to a proxy-authenticated IMAP stream
 *
 * @class    CAS_ProxiedService_Imap
 * @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_ProxiedService_Imap
extends CAS_ProxiedService_Abstract
{

    /**
     * The username to send via imap_open.
     *
     * @var string $_username;
     */
    private $_username;

    /**
     * Constructor.
     *
     * @param string $username Username
     *
     * @return void
     */
    public function __construct ($username)
    {
        if (!is_string($username) || !strlen($username)) {
            throw new CAS_InvalidArgumentException('Invalid username.');
        }

        $this->_username = $username;
    }

    /**
     * The target service url.
     * @var string $_url;
     */
    private $_url;

    /**
     * Answer a service identifier (URL) for whom we should fetch a proxy ticket.
     *
     * @return string
     * @throws Exception If no service url is available.
     */
    public function getServiceUrl ()
    {
        if (empty($this->_url)) {
            throw new CAS_ProxiedService_Exception(
                'No URL set via '.get_class($this).'->getServiceUrl($url).'
            );
        }

        return $this->_url;
    }

    /*********************************************************
     * Configure the Stream
    *********************************************************/

    /**
     * Set the URL of the service to pass to CAS for proxy-ticket retrieval.
     *
     * @param string $url Url to set
     *
     * @return void
     * @throws CAS_OutOfSequenceException If called after the stream has been opened.
     */
    public function setServiceUrl ($url)
    {
        if ($this->hasBeenOpened()) {
            throw new CAS_OutOfSequenceException(
                'Cannot set the URL, stream already opened.'
            );
        }
        if (!is_string($url) || !strlen($url)) {
            throw new CAS_InvalidArgumentException('Invalid url.');
        }

        $this->_url = $url;
    }

    /**
     * The mailbox to open. See the $mailbox parameter of imap_open().
     *
     * @var string $_mailbox
     */
    private $_mailbox;

    /**
     * Set the mailbox to open. See the $mailbox parameter of imap_open().
     *
     * @param string $mailbox Mailbox to set
     *
     * @return void
     * @throws CAS_OutOfSequenceException If called after the stream has been opened.
     */
    public function setMailbox ($mailbox)
    {
        if ($this->hasBeenOpened()) {
            throw new CAS_OutOfSequenceException(
                'Cannot set the mailbox, stream already opened.'
            );
        }
        if (!is_string($mailbox) || !strlen($mailbox)) {
            throw new CAS_InvalidArgumentException('Invalid mailbox.');
        }

        $this->_mailbox = $mailbox;
    }

    /**
     * A bit mask of options to pass to imap_open() as the $options parameter.
     *
     * @var int $_options
     */
    private $_options = null;

    /**
     * Set the options for opening the stream. See the $options parameter of
     * imap_open().
     *
     * @param int $options Options for the stream
     *
     * @return void
     * @throws CAS_OutOfSequenceException If called after the stream has been opened.
     */
    public function setOptions ($options)
    {
        if ($this->hasBeenOpened()) {
            throw new CAS_OutOfSequenceException(
                'Cannot set options, stream already opened.'
            );
        }
        if (!is_int($options)) {
            throw new CAS_InvalidArgumentException('Invalid options.');
        }

        $this->_options = $options;
    }

    /*********************************************************
     * 2. Open the stream
    *********************************************************/

    /**
     * Open the IMAP stream (similar to imap_open()).
     *
     * @return resource Returns an IMAP stream on success
     * @throws CAS_OutOfSequenceException If called multiple times.
     * @throws CAS_ProxyTicketException If there is a proxy-ticket failure.
     *		The code of the Exception will be one of:
     *			PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE
     *			PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE
     *			PHPCAS_SERVICE_PT_FAILURE
     * @throws CAS_ProxiedService_Exception If there is a failure sending the
     *         request to the target service.
     */
    public function open ()
    {
        if ($this->hasBeenOpened()) {
            throw new CAS_OutOfSequenceException('Stream already opened.');
        }
        if (empty($this->_mailbox)) {
            throw new CAS_ProxiedService_Exception(
                'You must specify a mailbox via '.get_class($this)
                .'->setMailbox($mailbox)'
            );
        }

        phpCAS::traceBegin();

        // Get our proxy ticket and append it to our URL.
        $this->initializeProxyTicket();
        phpCAS::trace('opening IMAP mailbox `'.$this->_mailbox.'\'...');
        $this->_stream = @imap_open(
            $this->_mailbox, $this->_username, $this->getProxyTicket(),
            $this->_options
        );
        if ($this->_stream) {
            phpCAS::trace('ok');
        } else {
            phpCAS::trace('could not open mailbox');
            // @todo add localization integration.
            $message = 'IMAP Error: '.$this->_url.' '. var_export(imap_errors(), true);
            phpCAS::trace($message);
            throw new CAS_ProxiedService_Exception($message);
        }

        phpCAS::traceEnd();
        return $this->_stream;
    }

    /**
     * Answer true if our request has been sent yet.
     *
     * @return bool
     */
    protected function hasBeenOpened ()
    {
        return !empty($this->_stream);
    }

    /*********************************************************
     * 3. Access the result
    *********************************************************/
    /**
     * The IMAP stream
     *
     * @var resource $_stream
     */
    private $_stream;

    /**
     * Answer the IMAP stream
     *
     * @return resource
     * @throws CAS_OutOfSequenceException if stream is not opened yet
     */
    public function getStream ()
    {
        if (!$this->hasBeenOpened()) {
            throw new CAS_OutOfSequenceException(
                'Cannot access stream, not opened yet.'
            );
        }
        return $this->_stream;
    }

    /**
     * CAS_Client::serviceMail() needs to return the proxy ticket for some reason,
     * so this method provides access to it.
     *
     * @return string
     * @throws CAS_OutOfSequenceException If called before the stream has been
     * opened.
     */
    public function getImapProxyTicket ()
    {
        if (!$this->hasBeenOpened()) {
            throw new CAS_OutOfSequenceException(
                'Cannot access errors, stream not opened yet.'
            );
        }
        return $this->getProxyTicket();
    }
}
?>


code:
<?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/ProxiedService/Testabel.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 interface defines methods that allow proxy-authenticated service handlers
 * to be tested in unit tests.
 *
 * Classes implementing this interface SHOULD store the CAS_Client passed and
 * initialize themselves with that client rather than via the static phpCAS
 * method. For example:
 *
 *		/ **
 *		 * Fetch our proxy ticket.
 *		 * /
 *		protected function initializeProxyTicket() {
 *			// Allow usage of a particular CAS_Client for unit testing.
 *			if (is_null($this->casClient))
 *				phpCAS::initializeProxiedService($this);
 *			else
 *				$this->casClient->initializeProxiedService($this);
 *		}
 *
 * @class    CAS_ProxiedService_Testabel
 * @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
 */
interface CAS_ProxiedService_Testable
{

    /**
     * Use a particular CAS_Client->initializeProxiedService() rather than the
     * static phpCAS::initializeProxiedService().
     *
     * This method should not be called in standard operation, but is needed for unit
     * testing.
     *
     * @param CAS_Client $casClient Cas client object
     *
     * @return void
     * @throws CAS_OutOfSequenceException If called after a proxy ticket has
     *         already been initialized/set.
     */
    public function setCasClient (CAS_Client $casClient);

}
?>


code:
<?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/ProxyChain.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
 */

/**
 * A normal proxy-chain definition that lists each level of the chain as either
 * a string or regular expression.
 *
 * @class    CAS_ProxyChain
 * @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_ProxyChain
implements CAS_ProxyChain_Interface
{

    protected $chain = array();

    /**
     * A chain is an array of strings or regexp strings that will be matched
     * against. Regexp will be matched with preg_match and strings will be
     * matched from the beginning. A string must fully match the beginning of
     * an proxy url. So you can define a full domain as acceptable or go further
     * down.
     * Proxies have to be defined in reverse from the service to the user. If a
     * user hits service A get proxied via B to service C the list of acceptable
     * proxies on C would be array(B,A);
     *
     * @param array $chain A chain of proxies
     */
    public function __construct(array $chain)
    {
        // Ensure that we have an indexed array
        $this->chain = array_values($chain);
    }

    /**
     * Match a list of proxies.
     *
     * @param array $list The list of proxies in front of this service.
     *
     * @return bool
     */
    public function matches(array $list)
    {
        $list = array_values($list);  // Ensure that we have an indexed array
        if ($this->isSizeValid($list)) {
            $mismatch = false;
            foreach ($this->chain as $i => $search) {
                $proxy_url = $list[$i];
                if (preg_match('/^\/.*\/[ixASUXu]*$/s', $search)) {
                    if (preg_match($search, $proxy_url)) {
                        phpCAS::trace(
                            "Found regexp " .  $search . " matching " . $proxy_url
                        );
                    } else {
                        phpCAS::trace(
                            "No regexp match " .  $search . " != " . $proxy_url
                        );
                        $mismatch = true;
                        break;
                    }
                } else {
                    if (strncasecmp($search, $proxy_url, strlen($search)) == 0) {
                        phpCAS::trace(
                            "Found string " .  $search . " matching " . $proxy_url
                        );
                    } else {
                        phpCAS::trace(
                            "No match " .  $search . " != " . $proxy_url
                        );
                        $mismatch = true;
                        break;
                    }
                }
            }
            if (!$mismatch) {
                phpCAS::trace("Proxy chain matches");
                return true;
            }
        } else {
            phpCAS::trace("Proxy chain skipped: size mismatch");
        }
        return false;
    }

    /**
     * Validate the size of the the list as compared to our chain.
     *
     * @param array $list List of proxies
     *
     * @return bool
     */
    protected function isSizeValid (array $list)
    {
        return (sizeof($this->chain) == sizeof($list));
    }
}


code:
<?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/ProxyChain/AllowedList.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
 */

/**
 * ProxyChain is a container for storing chains of valid proxies that can
 * be used to validate proxied requests to a service
 *
 * @class    CAS_ProxyChain_AllowedList
 * @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_ProxyChain_AllowedList
{

    private $_chains = array();

    /**
     * Check whether proxies are allowed by configuration
     *
     * @return bool
     */
    public function isProxyingAllowed()
    {
        return (count($this->_chains) > 0);
    }

    /**
     * Add a chain of proxies to the list of possible chains
     *
     * @param CAS_ProxyChain_Interface $chain A chain of proxies
     *
     * @return void
     */
    public function allowProxyChain(CAS_ProxyChain_Interface $chain)
    {
        $this->_chains[] = $chain;
    }

    /**
     * Check if the proxies found in the response match the allowed proxies
     *
     * @param array $proxies list of proxies to check
     *
     * @return bool whether the proxies match the allowed proxies
     */
    public function isProxyListAllowed(array $proxies)
    {
        phpCAS::traceBegin();
        if (empty($proxies)) {
            phpCAS::trace("No proxies were found in the response");
            phpCAS::traceEnd(true);
            return true;
        } elseif (!$this->isProxyingAllowed()) {
            phpCAS::trace("Proxies are not allowed");
            phpCAS::traceEnd(false);
            return false;
        } else {
            $res = $this->contains($proxies);
            phpCAS::traceEnd($res);
            return $res;
        }
    }

    /**
     * Validate the proxies from the proxy ticket validation against the
     * chains that were definded.
     *
     * @param array $list List of proxies from the proxy ticket validation.
     *
     * @return bool if any chain fully matches the supplied list
     */
    public function contains(array $list)
    {
        phpCAS::traceBegin();
        $count = 0;
        foreach ($this->_chains as $chain) {
            phpCAS::trace("Checking chain ". $count++);
            if ($chain->matches($list)) {
                phpCAS::traceEnd(true);
                return true;
            }
        }
        phpCAS::trace("No proxy chain matches.");
        phpCAS::traceEnd(false);
        return false;
    }
}
?>


code:
<?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/ProxyChain/Any.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
 */

/**
 * A proxy-chain definition that will match any list of proxies.
 *
 * Use this class for quick testing or in certain production screnarios you
 * might want to allow allow any other valid service to proxy your service.
 *
 * THIS CLASS IS HOWEVER NOT RECOMMENDED FOR PRODUCTION AND HAS SECURITY
 * IMPLICATIONS: YOU ARE ALLOWING ANY SERVICE TO ACT ON BEHALF OF A USER
 * ON THIS SERVICE.
 *
 * @class    CAS_ProxyChain_Any
 * @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_ProxyChain_Any
implements CAS_ProxyChain_Interface
{

    /**
     * Match a list of proxies.
     *
     * @param array $list The list of proxies in front of this service.
     *
     * @return bool
     */
    public function matches(array $list)
    {
        phpCAS::trace("Using CAS_ProxyChain_Any. No proxy validation is performed.");
        return true;
    }

}


code:
<?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/ProxyChain/Interface.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
 */

/**
 * An interface for classes that define a list of allowed proxies in front of
 * the current application.
 *
 * @class    CAS_ProxyChain_Interface
 * @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
 */
interface CAS_ProxyChain_Interface
{

    /**
     * Match a list of proxies.
     *
     * @param array $list The list of proxies in front of this service.
     *
     * @return bool
     */
    public function matches(array $list);

}


code:
<?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/ProxyChain/Trusted.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
 */

/**
 * A proxy-chain definition that defines a chain up to a trusted proxy and
 * delegates the resposibility of validating the rest of the chain to that
 * trusted proxy.
 *
 * @class    CAS_ProxyChain_Trusted
 * @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_ProxyChain_Trusted
extends CAS_ProxyChain
implements CAS_ProxyChain_Interface
{

    /**
     * Validate the size of the the list as compared to our chain.
     *
     * @param array $list list of proxies
     *
     * @return bool
     */
    protected function isSizeValid (array $list)
    {
        return (sizeof($this->chain) <= sizeof($list));
    }

}


code:
<?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
 *
 * @class    CAS/ProxyTicketException.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
 *
 */

/**
 * An Exception for errors related to fetching or validating proxy tickets.
 *
 * @class    CAS_ProxyTicketException
 * @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_ProxyTicketException
extends BadMethodCallException
implements CAS_Exception
{

    /**
     * Constructor
     *
     * @param string $message Message text
     * @param int    $code    Error code
     *
     * @return void
     */
    public function __construct ($message, $code = PHPCAS_SERVICE_PT_FAILURE)
    {
        // Warn if the code is not in our allowed list
        $ptCodes = array(
        PHPCAS_SERVICE_PT_FAILURE,
        PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE,
        PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE,
        );
        if (!in_array($code, $ptCodes)) {
            trigger_error(
                'Invalid code '.$code
                .' passed. Must be one of PHPCAS_SERVICE_PT_FAILURE, PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE, or PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE.'
            );
        }

        parent::__construct($message, $code);
    }
}


code:
<?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/Request/AbstractRequest.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
 */

/**
 * Provides support for performing web-requests via curl
 *
 * @class    CAS_Request_AbstractRequest
 * @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
 */
abstract class CAS_Request_AbstractRequest
implements CAS_Request_RequestInterface
{

    protected $url = null;
    protected $cookies = array();
    protected $headers = array();
    protected $isPost = false;
    protected $postBody = null;
    protected $caCertPath = null;
    protected $validateCN = true;
    private $_sent = false;
    private $_responseHeaders = array();
    private $_responseBody = null;
    private $_errorMessage = '';

    /*********************************************************
     * Configure the Request
    *********************************************************/

    /**
     * Set the URL of the Request
     *
     * @param string $url Url to set
     *
     * @return void
     * @throws CAS_OutOfSequenceException If called after the Request has been sent.
     */
    public function setUrl ($url)
    {
        if ($this->_sent) {
            throw new CAS_OutOfSequenceException(
                'Request has already been sent cannot '.__METHOD__
            );
        }

        $this->url = $url;
    }

    /**
     * Add a cookie to the request.
     *
     * @param string $name  Name of entry
     * @param string $value value of entry
     *
     * @return void
     * @throws CAS_OutOfSequenceException If called after the Request has been sent.
     */
    public function addCookie ($name, $value)
    {
        if ($this->_sent) {
            throw new CAS_OutOfSequenceException(
                'Request has already been sent cannot '.__METHOD__
            );
        }

        $this->cookies[$name] = $value;
    }

    /**
     * Add an array of cookies to the request.
     * The cookie array is of the form
     *     array('cookie_name' => 'cookie_value', 'cookie_name2' => cookie_value2')
     *
     * @param array $cookies cookies to add
     *
     * @return void
     * @throws CAS_OutOfSequenceException If called after the Request has been sent.
     */
    public function addCookies (array $cookies)
    {
        if ($this->_sent) {
            throw new CAS_OutOfSequenceException(
                'Request has already been sent cannot '.__METHOD__
            );
        }

        $this->cookies = array_merge($this->cookies, $cookies);
    }

    /**
     * Add a header string to the request.
     *
     * @param string $header Header to add
     *
     * @return void
     * @throws CAS_OutOfSequenceException If called after the Request has been sent.
     */
    public function addHeader ($header)
    {
        if ($this->_sent) {
            throw new CAS_OutOfSequenceException(
                'Request has already been sent cannot '.__METHOD__
            );
        }

        $this->headers[] = $header;
    }

    /**
     * Add an array of header strings to the request.
     *
     * @param array $headers headers to add
     *
     * @return void
     * @throws CAS_OutOfSequenceException If called after the Request has been sent.
     */
    public function addHeaders (array $headers)
    {
        if ($this->_sent) {
            throw new CAS_OutOfSequenceException(
                'Request has already been sent cannot '.__METHOD__
            );
        }

        $this->headers = array_merge($this->headers, $headers);
    }

    /**
     * Make the request a POST request rather than the default GET request.
     *
     * @return void
     * @throws CAS_OutOfSequenceException If called after the Request has been sent.
     */
    public function makePost ()
    {
        if ($this->_sent) {
            throw new CAS_OutOfSequenceException(
                'Request has already been sent cannot '.__METHOD__
            );
        }

        $this->isPost = true;
    }

    /**
     * Add a POST body to the request
     *
     * @param string $body body to add
     *
     * @return void
     * @throws CAS_OutOfSequenceException If called after the Request has been sent.
     */
    public function setPostBody ($body)
    {
        if ($this->_sent) {
            throw new CAS_OutOfSequenceException(
                'Request has already been sent cannot '.__METHOD__
            );
        }
        if (!$this->isPost) {
            throw new CAS_OutOfSequenceException(
                'Cannot add a POST body to a GET request, use makePost() first.'
            );
        }

        $this->postBody = $body;
    }

    /**
     * Specify the path to an SSL CA certificate to validate the server with.
     *
     * @param string $caCertPath  path to cert
     * @param bool   $validate_cn valdiate CN of certificate
     *
     * @return void
     * @throws CAS_OutOfSequenceException If called after the Request has been sent.
     */
    public function setSslCaCert ($caCertPath,$validate_cn=true)
    {
        if ($this->_sent) {
            throw new CAS_OutOfSequenceException(
                'Request has already been sent cannot '.__METHOD__
            );
        }
        $this->caCertPath = $caCertPath;
        $this->validateCN = $validate_cn;
    }

    /*********************************************************
     * 2. Send the Request
    *********************************************************/

    /**
     * Perform the request.
     *
     * @return bool TRUE on success, FALSE on failure.
     * @throws CAS_OutOfSequenceException If called multiple times.
     */
    public function send ()
    {
        if ($this->_sent) {
            throw new CAS_OutOfSequenceException(
                'Request has already been sent cannot send again.'
            );
        }
        if (is_null($this->url) || !$this->url) {
            throw new CAS_OutOfSequenceException(
                'A url must be specified via setUrl() before the request can be sent.'
            );
        }
        $this->_sent = true;
        return $this->sendRequest();
    }

    /**
     * Send the request and store the results.
     *
     * @return bool TRUE on success, FALSE on failure.
     */
    abstract protected function sendRequest ();

    /**
     * Store the response headers.
     *
     * @param array $headers headers to store
     *
     * @return void
     */
    protected function storeResponseHeaders (array $headers)
    {
        $this->_responseHeaders = array_merge($this->_responseHeaders, $headers);
    }

    /**
     * Store a single response header to our array.
     *
     * @param string $header header to store
     *
     * @return void
     */
    protected function storeResponseHeader ($header)
    {
        $this->_responseHeaders[] = $header;
    }

    /**
     * Store the response body.
     *
     * @param string $body body to store
     *
     * @return void
     */
    protected function storeResponseBody ($body)
    {
        $this->_responseBody = $body;
    }

    /**
     * Add a string to our error message.
     *
     * @param string $message message to add
     *
     * @return void
     */
    protected function storeErrorMessage ($message)
    {
        $this->_errorMessage .= $message;
    }

    /*********************************************************
     * 3. Access the response
    *********************************************************/

    /**
     * Answer the headers of the response.
     *
     * @return array An array of header strings.
     * @throws CAS_OutOfSequenceException If called before the Request has been sent.
     */
    public function getResponseHeaders ()
    {
        if (!$this->_sent) {
            throw new CAS_OutOfSequenceException(
                'Request has not been sent yet. Cannot '.__METHOD__
            );
        }
        return $this->_responseHeaders;
    }

    /**
     * Answer HTTP status code of the response
     *
     * @return int
     * @throws CAS_OutOfSequenceException If called before the Request has been sent.
     * @throws CAS_Request_Exception if the response did not contain a status code
     */
    public function getResponseStatusCode ()
    {
        if (!$this->_sent) {
            throw new CAS_OutOfSequenceException(
                'Request has not been sent yet. Cannot '.__METHOD__
            );
        }

        if (!preg_match(
            '/HTTP\/[0-9.]+\s+([0-9]+)\s*(.*)/',
            $this->_responseHeaders[0], $matches
        )
        ) {
            throw new CAS_Request_Exception(
                'Bad response, no status code was found in the first line.'
            );
        }

        return intval($matches[1]);
    }

    /**
     * Answer the body of response.
     *
     * @return string
     * @throws CAS_OutOfSequenceException If called before the Request has been sent.
     */
    public function getResponseBody ()
    {
        if (!$this->_sent) {
            throw new CAS_OutOfSequenceException(
                'Request has not been sent yet. Cannot '.__METHOD__
            );
        }

        return $this->_responseBody;
    }

    /**
     * Answer a message describing any errors if the request failed.
     *
     * @return string
     * @throws CAS_OutOfSequenceException If called before the Request has been sent.
     */
    public function getErrorMessage ()
    {
        if (!$this->_sent) {
            throw new CAS_OutOfSequenceException(
                'Request has not been sent yet. Cannot '.__METHOD__
            );
        }
        return $this->_errorMessage;
    }
}


code:
<?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/Request/AbstractRequest.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 interface defines a class library for performing multiple web requests
 * in batches. Implementations of this interface may perform requests serially
 * or in parallel.
 *
 * @class    CAS_Request_CurlMultiRequest
 * @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_Request_CurlMultiRequest
implements CAS_Request_MultiRequestInterface
{
    private $_requests = array();
    private $_sent = false;

    /*********************************************************
     * Add Requests
    *********************************************************/

    /**
     * Add a new Request to this batch.
     * Note, implementations will likely restrict requests to their own concrete
     * class hierarchy.
     *
     * @param CAS_Request_RequestInterface $request reqest to add
     *
     * @return void
     * @throws CAS_OutOfSequenceException If called after the Request has been sent.
     * @throws CAS_InvalidArgumentException If passed a Request of the wrong
     * implmentation.
     */
    public function addRequest (CAS_Request_RequestInterface $request)
    {
        if ($this->_sent) {
            throw new CAS_OutOfSequenceException(
                'Request has already been sent cannot '.__METHOD__
            );
        }
        if (!$request instanceof CAS_Request_CurlRequest) {
            throw new CAS_InvalidArgumentException(
                'As a CAS_Request_CurlMultiRequest, I can only work with CAS_Request_CurlRequest objects.'
            );
        }

        $this->_requests[] = $request;
    }

    /**
     * Retrieve the number of requests added to this batch.
     *
     * @return int number of request elements
     * @throws CAS_OutOfSequenceException if the request has already been sent
     */
    public function getNumRequests()
    {
        if ($this->_sent) {
            throw new CAS_OutOfSequenceException(
                'Request has already been sent cannot '.__METHOD__
            );
        }
        return count($this->_requests);
    }

    /*********************************************************
     * 2. Send the Request
    *********************************************************/

    /**
     * Perform the request. After sending, all requests will have their
     * responses poulated.
     *
     * @return bool TRUE on success, FALSE on failure.
     * @throws CAS_OutOfSequenceException If called multiple times.
     */
    public function send ()
    {
        if ($this->_sent) {
            throw new CAS_OutOfSequenceException(
                'Request has already been sent cannot send again.'
            );
        }
        if (!count($this->_requests)) {
            throw new CAS_OutOfSequenceException(
                'At least one request must be added via addRequest() before the multi-request can be sent.'
            );
        }

        $this->_sent = true;

        // Initialize our handles and configure all requests.
        $handles = array();
        $multiHandle = curl_multi_init();
        foreach ($this->_requests as $i => $request) {
            $handle = $request->initAndConfigure();
            curl_setopt($handle, CURLOPT_RETURNTRANSFER, true);
            $handles[$i] = $handle;
            curl_multi_add_handle($multiHandle, $handle);
        }

        // Execute the requests in parallel.
        do {
            curl_multi_exec($multiHandle, $running);
        } while ($running > 0);

        // Populate all of the responses or errors back into the request objects.
        foreach ($this->_requests as $i => $request) {
            $buf = curl_multi_getcontent($handles[$i]);
            $request->_storeResponseBody($buf);
            curl_multi_remove_handle($multiHandle, $handles[$i]);
            curl_close($handles[$i]);
        }

        curl_multi_close($multiHandle);
    }
}


code:
<?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/Request/CurlRequest.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
 */

/**
 * Provides support for performing web-requests via curl
 *
 * @class    CAS_Request_CurlRequest
 * @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_Request_CurlRequest
extends CAS_Request_AbstractRequest
implements CAS_Request_RequestInterface
{

    /**
     * Set additional curl options
     *
     * @param array $options option to set
     *
     * @return void
     */
    public function setCurlOptions (array $options)
    {
        $this->_curlOptions = $options;
    }
    private $_curlOptions = array();

    /**
     * Send the request and store the results.
     *
     * @return bool true on success, false on failure.
     */
    protected function sendRequest ()
    {
        phpCAS::traceBegin();

        /*********************************************************
         * initialize the CURL session
        *********************************************************/
        $ch = $this->initAndConfigure();

        /*********************************************************
         * Perform the query
        *********************************************************/
        $buf = curl_exec($ch);
        if ( $buf === false ) {
            phpCAS::trace('curl_exec() failed');
            $this->storeErrorMessage(
                'CURL error #'.curl_errno($ch).': '.curl_error($ch)
            );
            $res = false;
        } else {
            $this->storeResponseBody($buf);
            phpCAS::trace("Response Body: \n".$buf."\n");
            $res = true;

        }
        // close the CURL session
        curl_close($ch);

        phpCAS::traceEnd($res);
        return $res;
    }

    /**
     * Internal method to initialize our cURL handle and configure the request.
     * This method should NOT be used outside of the CurlRequest or the
     * CurlMultiRequest.
     *
     * @return resource|false The cURL handle on success, false on failure
     */
    public function initAndConfigure()
    {
        /*********************************************************
         * initialize the CURL session
        *********************************************************/
        $ch = curl_init($this->url);

        if (version_compare(PHP_VERSION, '5.1.3', '>=')) {
            //only avaible in php5
            curl_setopt_array($ch, $this->_curlOptions);
        } else {
            foreach ($this->_curlOptions as $key => $value) {
                curl_setopt($ch, $key, $value);
            }
        }

        /*********************************************************
         * Set SSL configuration
        *********************************************************/
        if ($this->caCertPath) {
            if ($this->validateCN) {
                curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
            } else {
                curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
            }
            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 1);
            curl_setopt($ch, CURLOPT_CAINFO, $this->caCertPath);
            phpCAS::trace('CURL: Set CURLOPT_CAINFO ' . $this->caCertPath);
        } else {
            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
            curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
        }

        /*********************************************************
         * Configure curl to capture our output.
        *********************************************************/
        // return the CURL output into a variable
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);

        // get the HTTP header with a callback
        curl_setopt($ch, CURLOPT_HEADERFUNCTION, array($this, '_curlReadHeaders'));

        /*********************************************************
         * Add cookie headers to our request.
        *********************************************************/
        if (count($this->cookies)) {
            $cookieStrings = array();
            foreach ($this->cookies as $name => $val) {
                $cookieStrings[] = $name.'='.$val;
            }
            curl_setopt($ch, CURLOPT_COOKIE, implode(';', $cookieStrings));
        }

        /*********************************************************
         * Add any additional headers
        *********************************************************/
        if (count($this->headers)) {
            curl_setopt($ch, CURLOPT_HTTPHEADER, $this->headers);
        }

        /*********************************************************
         * Flag and Body for POST requests
        *********************************************************/
        if ($this->isPost) {
            curl_setopt($ch, CURLOPT_POST, 1);
            curl_setopt($ch, CURLOPT_POSTFIELDS, $this->postBody);
        }

        return $ch;
    }

    /**
     * Store the response body.
     * This method should NOT be used outside of the CurlRequest or the
     * CurlMultiRequest.
     *
     * @param string $body body to stor
     *
     * @return void
     */
    private function _storeResponseBody ($body)
    {
        $this->storeResponseBody($body);
    }

    /**
     * Internal method for capturing the headers from a curl request.
     *
     * @param resource $ch     handle of curl
     * @param string $header header
     *
     * @return int
     */
    private function _curlReadHeaders ($ch, $header)
    {
        $this->storeResponseHeader($header);
        return strlen($header);
    }
}


code:
<?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/Request/Exception.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
 */

/**
 * An Exception for problems performing requests
 *
 * @class    CAS_Request_Exception
 * @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_Request_Exception
extends Exception
implements CAS_Exception
{

}


code:
<?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/Request/MultiRequestInterface.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 interface defines a class library for performing multiple web requests
 * in batches. Implementations of this interface may perform requests serially
 * or in parallel.
 *
 * @class    CAS_Request_MultiRequestInterface
 * @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
 */
interface CAS_Request_MultiRequestInterface
{

    /*********************************************************
     * Add Requests
    *********************************************************/

    /**
     * Add a new Request to this batch.
     * Note, implementations will likely restrict requests to their own concrete
     * class hierarchy.
     *
     * @param CAS_Request_RequestInterface $request request interface
     *
     * @return void
     * @throws CAS_OutOfSequenceException If called after the Request has been
     * sent.
     * @throws CAS_InvalidArgumentException If passed a Request of the wrong
     * implmentation.
     */
    public function addRequest (CAS_Request_RequestInterface $request);

    /**
     * Retrieve the number of requests added to this batch.
     *
     * @return int number of request elements
     */
    public function getNumRequests ();

    /*********************************************************
     * 2. Send the Request
    *********************************************************/

    /**
     * Perform the request. After sending, all requests will have their
     * responses poulated.
     *
     * @return bool TRUE on success, FALSE on failure.
     * @throws CAS_OutOfSequenceException If called multiple times.
     */
    public function send ();
}


code:
<?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/Request/RequestInterface.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 interface defines a class library for performing web requests.
 *
 * @class    CAS_Request_RequestInterface
 * @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
 */
interface CAS_Request_RequestInterface
{

    /*********************************************************
     * Configure the Request
    *********************************************************/

    /**
     * Set the URL of the Request
     *
     * @param string $url url to set
     *
     * @return void
     * @throws CAS_OutOfSequenceException If called after the Request has been sent.
     */
    public function setUrl ($url);

    /**
     * Add a cookie to the request.
     *
     * @param string $name  name of cookie
     * @param string $value value of cookie
     *
     * @return void
     * @throws CAS_OutOfSequenceException If called after the Request has been sent.
     */
    public function addCookie ($name, $value);

    /**
     * Add an array of cookies to the request.
     * The cookie array is of the form
     *     array('cookie_name' => 'cookie_value', 'cookie_name2' => cookie_value2')
     *
     * @param array $cookies cookies to add
     *
     * @return void
     * @throws CAS_OutOfSequenceException If called after the Request has been sent.
     */
    public function addCookies (array $cookies);

    /**
     * Add a header string to the request.
     *
     * @param string $header header to add
     *
     * @return void
     * @throws CAS_OutOfSequenceException If called after the Request has been sent.
     */
    public function addHeader ($header);

    /**
     * Add an array of header strings to the request.
     *
     * @param array $headers headers to add
     *
     * @return void
     * @throws CAS_OutOfSequenceException If called after the Request has been sent.
     */
    public function addHeaders (array $headers);

    /**
     * Make the request a POST request rather than the default GET request.
     *
     * @return void
     * @throws CAS_OutOfSequenceException If called after the Request has been sent.
     */
    public function makePost ();

    /**
     * Add a POST body to the request
     *
     * @param string $body body to add
     *
     * @return void
     * @throws CAS_OutOfSequenceException If called after the Request has been sent.
     */
    public function setPostBody ($body);

    /**
     * Specify the path to an SSL CA certificate to validate the server with.
     *
     * @param string  $caCertPath  path to cert file
     * @param boolean $validate_cn validate CN of SSL certificate
     *
     * @return void
     * @throws CAS_OutOfSequenceException If called after the Request has been sent.
     */
    public function setSslCaCert ($caCertPath, $validate_cn = true);

    /*********************************************************
     * 2. Send the Request
    *********************************************************/

    /**
     * Perform the request.
     *
     * @return bool TRUE on success, FALSE on failure.
     * @throws CAS_OutOfSequenceException If called multiple times.
     */
    public function send ();

    /*********************************************************
     * 3. Access the response
    *********************************************************/

    /**
     * Answer the headers of the response.
     *
     * @return array An array of header strings.
     * @throws CAS_OutOfSequenceException If called before the Request has been sent.
     */
    public function getResponseHeaders ();

    /**
     * Answer HTTP status code of the response
     *
     * @return int
     * @throws CAS_OutOfSequenceException If called before the Request has been sent.
     */
    public function getResponseStatusCode ();

    /**
     * Answer the body of response.
     *
     * @return string
     * @throws CAS_OutOfSequenceException If called before the Request has been sent.
     */
    public function getResponseBody ();

    /**
     * Answer a message describing any errors if the request failed.
     *
     * @return string
     * @throws CAS_OutOfSequenceException If called before the Request has been sent.
     */
    public function getErrorMessage ();
}


code:
<?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/InvalidArgumentException.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
 */

/**
 * Exception that denotes invalid arguments were passed.
 *
 * @class    CAS_InvalidArgumentException
 * @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_TypeMismatchException
extends CAS_InvalidArgumentException
{
    /**
     * Constructor, provides a nice message.
     *
     * @param mixed   $argument     Argument
     * @param string  $argumentName Argument Name
     * @param string  $type         Type
     * @param string  $message      Error Message
     * @param integer $code         Code
     *
     * @return void
     */
    public function __construct (
        $argument, $argumentName, $type, $message = '', $code = 0
    ) {
        if (is_object($argument)) {
            $foundType = get_class($argument).' object';
        } else {
            $foundType = gettype($argument);
        }

        parent::__construct(
            'type mismatched for parameter '
            . $argumentName . ' (should be \'' . $type .' \'), '
            . $foundType . ' given. ' . $message, $code
        );
    }
}
?>


code:
<?php

// autoload.php @generated by Composer

require_once __DIR__ . '/composer/autoload_real.php';

return ComposerAutoloaderInit444c3f31864f68a3f466e2c19837e185::getLoader();


code:
<?php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Autoload;

/**
 * ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
 *
 *     $loader = new \Composer\Autoload\ClassLoader();
 *
 *     // register classes with namespaces
 *     $loader->add('Symfony\Component', __DIR__.'/component');
 *     $loader->add('Symfony',           __DIR__.'/framework');
 *
 *     // activate the autoloader
 *     $loader->register();
 *
 *     // to enable searching the include path (eg. for PEAR packages)
 *     $loader->setUseIncludePath(true);
 *
 * In this example, if you try to use a class in the Symfony\Component
 * namespace or one of its children (Symfony\Component\Console for instance),
 * the autoloader will first look for the class under the component/
 * directory, and it will then fallback to the framework/ directory if not
 * found before giving up.
 *
 * This class is loosely based on the Symfony UniversalClassLoader.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @see    http://www.php-fig.org/psr/psr-0/
 * @see    http://www.php-fig.org/psr/psr-4/
 */
class ClassLoader
{
    // PSR-4
    private $prefixLengthsPsr4 = array();
    private $prefixDirsPsr4 = array();
    private $fallbackDirsPsr4 = array();

    // PSR-0
    private $prefixesPsr0 = array();
    private $fallbackDirsPsr0 = array();

    private $useIncludePath = false;
    private $classMap = array();
    private $classMapAuthoritative = false;
    private $missingClasses = array();
    private $apcuPrefix;

    public function getPrefixes()
    {
        if (!empty($this->prefixesPsr0)) {
            return call_user_func_array('array_merge', $this->prefixesPsr0);
        }

        return array();
    }

    public function getPrefixesPsr4()
    {
        return $this->prefixDirsPsr4;
    }

    public function getFallbackDirs()
    {
        return $this->fallbackDirsPsr0;
    }

    public function getFallbackDirsPsr4()
    {
        return $this->fallbackDirsPsr4;
    }

    public function getClassMap()
    {
        return $this->classMap;
    }

    /**
     * @param array $classMap Class to filename map
     */
    public function addClassMap(array $classMap)
    {
        if ($this->classMap) {
            $this->classMap = array_merge($this->classMap, $classMap);
        } else {
            $this->classMap = $classMap;
        }
    }

    /**
     * Registers a set of PSR-0 directories for a given prefix, either
     * appending or prepending to the ones previously set for this prefix.
     *
     * @param string       $prefix  The prefix
     * @param array|string $paths   The PSR-0 root directories
     * @param bool         $prepend Whether to prepend the directories
     */
    public function add($prefix, $paths, $prepend = false)
    {
        if (!$prefix) {
            if ($prepend) {
                $this->fallbackDirsPsr0 = array_merge(
                    (array) $paths,
                    $this->fallbackDirsPsr0
                );
            } else {
                $this->fallbackDirsPsr0 = array_merge(
                    $this->fallbackDirsPsr0,
                    (array) $paths
                );
            }

            return;
        }

        $first = $prefix[0];
        if (!isset($this->prefixesPsr0[$first][$prefix])) {
            $this->prefixesPsr0[$first][$prefix] = (array) $paths;

            return;
        }
        if ($prepend) {
            $this->prefixesPsr0[$first][$prefix] = array_merge(
                (array) $paths,
                $this->prefixesPsr0[$first][$prefix]
            );
        } else {
            $this->prefixesPsr0[$first][$prefix] = array_merge(
                $this->prefixesPsr0[$first][$prefix],
                (array) $paths
            );
        }
    }

    /**
     * Registers a set of PSR-4 directories for a given namespace, either
     * appending or prepending to the ones previously set for this namespace.
     *
     * @param string       $prefix  The prefix/namespace, with trailing '\\'
     * @param array|string $paths   The PSR-4 base directories
     * @param bool         $prepend Whether to prepend the directories
     *
     * @throws \InvalidArgumentException
     */
    public function addPsr4($prefix, $paths, $prepend = false)
    {
        if (!$prefix) {
            // Register directories for the root namespace.
            if ($prepend) {
                $this->fallbackDirsPsr4 = array_merge(
                    (array) $paths,
                    $this->fallbackDirsPsr4
                );
            } else {
                $this->fallbackDirsPsr4 = array_merge(
                    $this->fallbackDirsPsr4,
                    (array) $paths
                );
            }
        } elseif (!isset($this->prefixDirsPsr4[$prefix])) {
            // Register directories for a new namespace.
            $length = strlen($prefix);
            if ('\\' !== $prefix[$length - 1]) {
                throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
            }
            $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
            $this->prefixDirsPsr4[$prefix] = (array) $paths;
        } elseif ($prepend) {
            // Prepend directories for an already registered namespace.
            $this->prefixDirsPsr4[$prefix] = array_merge(
                (array) $paths,
                $this->prefixDirsPsr4[$prefix]
            );
        } else {
            // Append directories for an already registered namespace.
            $this->prefixDirsPsr4[$prefix] = array_merge(
                $this->prefixDirsPsr4[$prefix],
                (array) $paths
            );
        }
    }

    /**
     * Registers a set of PSR-0 directories for a given prefix,
     * replacing any others previously set for this prefix.
     *
     * @param string       $prefix The prefix
     * @param array|string $paths  The PSR-0 base directories
     */
    public function set($prefix, $paths)
    {
        if (!$prefix) {
            $this->fallbackDirsPsr0 = (array) $paths;
        } else {
            $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
        }
    }

    /**
     * Registers a set of PSR-4 directories for a given namespace,
     * replacing any others previously set for this namespace.
     *
     * @param string       $prefix The prefix/namespace, with trailing '\\'
     * @param array|string $paths  The PSR-4 base directories
     *
     * @throws \InvalidArgumentException
     */
    public function setPsr4($prefix, $paths)
    {
        if (!$prefix) {
            $this->fallbackDirsPsr4 = (array) $paths;
        } else {
            $length = strlen($prefix);
            if ('\\' !== $prefix[$length - 1]) {
                throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
            }
            $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
            $this->prefixDirsPsr4[$prefix] = (array) $paths;
        }
    }

    /**
     * Turns on searching the include path for class files.
     *
     * @param bool $useIncludePath
     */
    public function setUseIncludePath($useIncludePath)
    {
        $this->useIncludePath = $useIncludePath;
    }

    /**
     * Can be used to check if the autoloader uses the include path to check
     * for classes.
     *
     * @return bool
     */
    public function getUseIncludePath()
    {
        return $this->useIncludePath;
    }

    /**
     * Turns off searching the prefix and fallback directories for classes
     * that have not been registered with the class map.
     *
     * @param bool $classMapAuthoritative
     */
    public function setClassMapAuthoritative($classMapAuthoritative)
    {
        $this->classMapAuthoritative = $classMapAuthoritative;
    }

    /**
     * Should class lookup fail if not found in the current class map?
     *
     * @return bool
     */
    public function isClassMapAuthoritative()
    {
        return $this->classMapAuthoritative;
    }

    /**
     * APCu prefix to use to cache found/not-found classes, if the extension is enabled.
     *
     * @param string|null $apcuPrefix
     */
    public function setApcuPrefix($apcuPrefix)
    {
        $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
    }

    /**
     * The APCu prefix in use, or null if APCu caching is not enabled.
     *
     * @return string|null
     */
    public function getApcuPrefix()
    {
        return $this->apcuPrefix;
    }

    /**
     * Registers this instance as an autoloader.
     *
     * @param bool $prepend Whether to prepend the autoloader or not
     */
    public function register($prepend = false)
    {
        spl_autoload_register(array($this, 'loadClass'), true, $prepend);
    }

    /**
     * Unregisters this instance as an autoloader.
     */
    public function unregister()
    {
        spl_autoload_unregister(array($this, 'loadClass'));
    }

    /**
     * Loads the given class or interface.
     *
     * @param  string    $class The name of the class
     * @return bool|null True if loaded, null otherwise
     */
    public function loadClass($class)
    {
        if ($file = $this->findFile($class)) {
            includeFile($file);

            return true;
        }
    }

    /**
     * Finds the path to the file where the class is defined.
     *
     * @param string $class The name of the class
     *
     * @return string|false The path if found, false otherwise
     */
    public function findFile($class)
    {
        // class map lookup
        if (isset($this->classMap[$class])) {
            return $this->classMap[$class];
        }
        if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
            return false;
        }
        if (null !== $this->apcuPrefix) {
            $file = apcu_fetch($this->apcuPrefix.$class, $hit);
            if ($hit) {
                return $file;
            }
        }

        $file = $this->findFileWithExtension($class, '.php');

        // Search for Hack files if we are running on HHVM
        if (false === $file && defined('HHVM_VERSION')) {
            $file = $this->findFileWithExtension($class, '.hh');
        }

        if (null !== $this->apcuPrefix) {
            apcu_add($this->apcuPrefix.$class, $file);
        }

        if (false === $file) {
            // Remember that this class does not exist.
            $this->missingClasses[$class] = true;
        }

        return $file;
    }

    private function findFileWithExtension($class, $ext)
    {
        // PSR-4 lookup
        $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;

        $first = $class[0];
        if (isset($this->prefixLengthsPsr4[$first])) {
            $subPath = $class;
            while (false !== $lastPos = strrpos($subPath, '\\')) {
                $subPath = substr($subPath, 0, $lastPos);
                $search = $subPath . '\\';
                if (isset($this->prefixDirsPsr4[$search])) {
                    $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
                    foreach ($this->prefixDirsPsr4[$search] as $dir) {
                        if (file_exists($file = $dir . $pathEnd)) {
                            return $file;
                        }
                    }
                }
            }
        }

        // PSR-4 fallback dirs
        foreach ($this->fallbackDirsPsr4 as $dir) {
            if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
                return $file;
            }
        }

        // PSR-0 lookup
        if (false !== $pos = strrpos($class, '\\')) {
            // namespaced class name
            $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
                . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
        } else {
            // PEAR-like class name
            $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
        }

        if (isset($this->prefixesPsr0[$first])) {
            foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
                if (0 === strpos($class, $prefix)) {
                    foreach ($dirs as $dir) {
                        if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
                            return $file;
                        }
                    }
                }
            }
        }

        // PSR-0 fallback dirs
        foreach ($this->fallbackDirsPsr0 as $dir) {
            if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
                return $file;
            }
        }

        // PSR-0 include paths.
        if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
            return $file;
        }

        return false;
    }
}

/**
 * Scope isolated include.
 *
 * Prevents access to $this/self from included files.
 */
function includeFile($file)
{
    include $file;
}