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.
    }
}
?>

repo_name: spookie/gnu

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:
language: php

php:
# - 5.3 # requires old distro, see below
  - 5.4
  - 5.5
  - 5.6
  - 7.0
  - 7.1
  - 7.2
  - hhvm # ignore errors, see below

# lock distro so new future defaults will not break the build
dist: trusty

matrix:
  include:
    - php: 5.3
      dist: precise
  allow_failures:
    - php: hhvm

install:
  - composer install --no-interaction

script:
  - vendor/bin/phpunit --coverage-text


code:
# Changelog

## 1.4.1 (2019-04-09)

*   Fix: Check if the function is declared before declaring it.
    (#23 by @Niko9911)

*   Improve test suite to also test against PHP 7.2 and
    add test for base64 encoding and decoding filters.
    (#22 by @arubacao and #25 by @Nyholm and @clue)

## 1.4.0 (2017-08-18)

*   Feature / Fix: The `fun()` function does not pass filter parameter `null`
    to underlying `stream_filter_append()` by default
    (#15 by @Nyholm)

    Certain filters (such as `convert.quoted-printable-encode`) do not accept
    a filter parameter at all. If no explicit filter parameter is given, we no
    longer pass a default `null` value.

    ```php
    $encode = Filter\fun('convert.quoted-printable-encode');
    assert('t=C3=A4st' === $encode('täst'));
    ```

*   Add examples and improve documentation
    (#13 and #20 by @clue and #18 by @Nyholm)

*   Improve test suite by adding PHPUnit to require-dev,
    fix HHVM build for now again and ignore future HHVM build errors,
    lock Travis distro so new future defaults will not break the build
    and test on PHP 7.1
    (#12, #14 and #19 by @clue and #16 by @Nyholm)

## 1.3.0 (2015-11-08)

*   Feature: Support accessing built-in filters as callbacks
    (#5 by @clue)

    ```php
    $fun = Filter\fun('zlib.deflate');

    $ret = $fun('hello') . $fun('world') . $fun();
    assert('helloworld' === gzinflate($ret));
    ```

## 1.2.0 (2015-10-23)

* Feature: Invoke close event when closing filter (flush buffer)
  (#9 by @clue)

## 1.1.0 (2015-10-22)

* Feature: Abort filter operation when catching an Exception
  (#10 by @clue)

* Feature: Additional safeguards to prevent filter state corruption
  (#7 by @clue)

## 1.0.0 (2015-10-18)

* First tagged release


code:
# clue/stream-filter [![Build Status](https://travis-ci.org/clue/php-stream-filter.svg?branch=master)](https://travis-ci.org/clue/php-stream-filter)

A simple and modern approach to stream filtering in PHP

**Table of contents**

* [Why?](#why)
* [Usage](#usage)
  * [append()](#append)
  * [prepend()](#prepend)
  * [fun()](#fun)
  * [remove()](#remove)
* [Install](#install)
* [Tests](#tests)
* [License](#license)

## Why?

PHP's stream filtering system is great!

It offers very powerful stream filtering options and comes with a useful set of built-in filters.
These filters can be used to easily and efficiently perform various transformations on-the-fly, such as:

* read from a gzip'ed input file,
* transcode from ISO-8859-1 (Latin1) to UTF-8,
* write to a bzip output file
* and much more.

But let's face it:
Its API is [*difficult to work with*](https://www.php.net/manual/en/php-user-filter.filter.php)
and its documentation is [*subpar*](https://stackoverflow.com/questions/27103269/what-is-a-bucket-brigade).
This combined means its powerful features are often neglected.

This project aims to make these features more accessible to a broader audience.
* **Lightweight, SOLID design** -
  Provides a thin abstraction that is [*just good enough*](https://en.wikipedia.org/wiki/Principle_of_good_enough)
  and does not get in your way.
  Custom filters require trivial effort.
* **Good test coverage** -
  Comes with an automated tests suite and is regularly tested in the *real world*

## Usage

This lightweight library consists only of a few simple functions.
All functions reside under the `Clue\StreamFilter` namespace.

The below examples assume you use an import statement similar to this:

```php
use Clue\StreamFilter as Filter;

Filter\append(…);
```

Alternatively, you can also refer to them with their fully-qualified name:

```php
\Clue\StreamFilter\append(…);
```

### append()

The `append($stream, $callback, $read_write = STREAM_FILTER_ALL)` function can be used to
append a filter callback to the given stream.

Each stream can have a list of filters attached.
This function appends a filter to the end of this list.

This function returns a filter resource which can be passed to [`remove()`](#remove).
If the given filter can not be added, it throws an `Exception`.

The `$stream` can be any valid stream resource, such as:

```php
$stream = fopen('demo.txt', 'w+');
```

The `$callback` should be a valid callable function which accepts an individual chunk of data
and should return the updated chunk:

```php
$filter = Filter\append($stream, function ($chunk) {
    // will be called each time you read or write a $chunk to/from the stream
    return $chunk;
});
```

As such, you can also use native PHP functions or any other `callable`:

```php
Filter\append($stream, 'strtoupper');

// will write "HELLO" to the underlying stream
fwrite($stream, 'hello');
```

If the `$callback` accepts invocation without parameters, then this signature
will be invoked once ending (flushing) the filter:

```php
Filter\append($stream, function ($chunk = null) {
    if ($chunk === null) {
        // will be called once ending the filter
        return 'end';
    }
    // will be called each time you read or write a $chunk to/from the stream
    return $chunk;
});

fclose($stream);
```

> Note: Legacy PHP versions (PHP < 5.4) do not support passing additional data
from the end signal handler if the stream is being closed.

If your callback throws an `Exception`, then the filter process will be aborted.
In order to play nice with PHP's stream handling, the `Exception` will be
transformed to a PHP warning instead:

```php
Filter\append($stream, function ($chunk) {
    throw new \RuntimeException('Unexpected chunk');
});

// raises an E_USER_WARNING with "Error invoking filter: Unexpected chunk"
fwrite($stream, 'hello');
```

The optional `$read_write` parameter can be used to only invoke the `$callback` when either writing to the stream or only when reading from the stream:

```php
Filter\append($stream, function ($chunk) {
    // will be called each time you write to the stream
    return $chunk;
}, STREAM_FILTER_WRITE);

Filter\append($stream, function ($chunk) {
    // will be called each time you read from the stream
    return $chunk;
}, STREAM_FILTER_READ);
```

> Note that once a filter has been added to stream, the stream can no longer be passed to
> [`stream_select()`](https://www.php.net/manual/en/function.stream-select.php)
> (and family).
>
> > Warning: stream_select(): cannot cast a filtered stream on this system in {file} on line {line}
>
> This is due to limitations of PHP's stream filter support, as it can no longer reliably
> tell when the underlying stream resource is actually ready.
> As an alternative, consider calling `stream_select()` on the unfiltered stream and
> then pass the unfiltered data through the [`fun()`](#fun) function.

### prepend()

The `prepend($stream, $callback, $read_write = STREAM_FILTER_ALL)` function can be used to
prepend a filter callback to the given stream.

Each stream can have a list of filters attached.
This function prepends a filter to the start of this list.

This function returns a filter resource which can be passed to [`remove()`](#remove).
If the given filter can not be added, it throws an `Exception`.

```php
$filter = Filter\prepend($stream, function ($chunk) {
    // will be called each time you read or write a $chunk to/from the stream
    return $chunk;
});
```

Except for the position in the list of filters, this function behaves exactly
like the [`append()`](#append) function.
For more details about its behavior, see also the [`append()`](#append) function.

### fun()

The `fun($filter, $parameters = null)` function can be used to
create a filter function which uses the given built-in `$filter`.

PHP comes with a useful set of [built-in filters](https://www.php.net/manual/en/filters.php).
Using `fun()` makes accessing these as easy as passing an input string to filter
and getting the filtered output string.

```php
$fun = Filter\fun('string.rot13');

assert('grfg' === $fun('test'));
assert('test' === $fun($fun('test'));
```

Please note that not all filter functions may be available depending on installed
PHP extensions and the PHP version in use.
In particular, [HHVM](https://hhvm.com/) may not offer the same filter functions
or parameters as Zend PHP.
Accessing an unknown filter function will result in a `RuntimeException`:

```php
Filter\fun('unknown'); // throws RuntimeException
```

Some filters may accept or require additional filter parameters – most
filters do not require filter parameters.
If given, the optional `$parameters` argument will be passed to the
underlying filter handler as-is.
In particular, note how *not passing* this parameter at all differs from
explicitly passing a `null` value (which many filters do not accept).
Please refer to the individual filter definition for more details.
For example, the `string.strip_tags` filter can be invoked like this:

```php
$fun = Filter\fun('string.strip_tags', '<a><b>');

$ret = $fun('<b>h<br>i</b>');
assert('<b>hi</b>' === $ret);
```

Under the hood, this function allocates a temporary memory stream, so it's
recommended to clean up the filter function after use.
Also, some filter functions (in particular the
[zlib compression filters](https://www.php.net/manual/en/filters.compression.php))
may use internal buffers and may emit a final data chunk on close.
The filter function can be closed by invoking without any arguments:

```php
$fun = Filter\fun('zlib.deflate');

$ret = $fun('hello') . $fun('world') . $fun();
assert('helloworld' === gzinflate($ret));
```

The filter function must not be used anymore after it has been closed.
Doing so will result in a `RuntimeException`:

```php
$fun = Filter\fun('string.rot13');
$fun();

$fun('test'); // throws RuntimeException
```

> Note: If you're using the zlib compression filters, then you should be wary
about engine inconsistencies between different PHP versions and HHVM.
These inconsistencies exist in the underlying PHP engines and there's little we
can do about this in this library.
[Our test suite](tests/) contains several test cases that exhibit these issues.
If you feel some test case is missing or outdated, we're happy to accept PRs! :)

### remove()

The `remove($filter)` function can be used to
remove a filter previously added via [`append()`](#append) or [`prepend()`](#prepend).

```php
$filter = Filter\append($stream, function () {
    // …
});
Filter\remove($filter);
```

## Install

The recommended way to install this library is [through Composer](https://getcomposer.org).
[New to Composer?](https://getcomposer.org/doc/00-intro.md)

This project follows [SemVer](https://semver.org/).
This will install the latest supported version:

```bash
$ composer require clue/stream-filter:^1.4.1
```

See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades.

This project aims to run on any platform and thus does not require any PHP
extensions and supports running on legacy PHP 5.3 through current PHP 7+ and
HHVM.
It's *highly recommended to use PHP 7+* for this project.
Older PHP versions may suffer from a number of inconsistencies documented above.

## Tests

To run the test suite, you first need to clone this repo and then install all
dependencies [through Composer](https://getcomposer.org):

```bash
$ composer install
```

To run the test suite, go to the project root and run:

```bash
$ php vendor/bin/phpunit
```

## License

This project is released under the permissive [MIT license](LICENSE).

> Did you know that I offer custom development services and issuing invoices for
  sponsorships of releases and for contributions? Contact me (@clue) for details.


clue/stream-filter

description: A simple and modern approach to stream filtering in PHP

license: MIT

Clue\StreamFilter\: src/


code:
<?php

// $ echo test | php examples/base64_encode.php | php examples/base64_decode.php

require __DIR__ . '/../vendor/autoload.php';

// decoding requires buffering in chunks of 4 bytes each
$buffer = '';
Clue\StreamFilter\append(STDIN, function ($chunk = null) use (&$buffer) {
    if ($chunk === null) {
        if (strlen($buffer) % 4 !== 0) {
            throw new \UnexpectedValueException('Invalid length');
        }
        $chunk = $buffer;
    } else {
        $buffer .= $chunk;
        $len = strlen($buffer) - (strlen($buffer) % 4);
        $chunk = (string)substr($buffer, 0, $len);
        $buffer = (string)substr($buffer, $len);
    }

    $ret = base64_decode($chunk, true);
    if ($ret === false) {
        throw new \UnexpectedValueException('Not a valid base64 encoded chunk');
    }
    return $ret;
}, STREAM_FILTER_READ);

fpassthru(STDIN);


code:
<?php

// $ echo test | php examples/base64_encode.php | base64 --decode

require __DIR__ . '/../vendor/autoload.php';

// encoding requires buffering in chunks of 3 bytes each
$buffer = '';
Clue\StreamFilter\append(STDIN, function ($chunk = null) use (&$buffer) {
    if ($chunk === null) {
        return base64_encode($buffer);
    }
    $buffer .= $chunk;
    $len = strlen($buffer) - (strlen($buffer) % 3);
    $chunk = substr($buffer, 0, $len);
    $buffer = substr($buffer, $len);

    return base64_encode($chunk);
}, STREAM_FILTER_READ);

fpassthru(STDIN);


code:
<?php

// $ echo test | php examples/uppercase.php

require __DIR__ . '/../vendor/autoload.php';

Clue\StreamFilter\append(STDIN, 'strtoupper');

fpassthru(STDIN);


code:
<?xml version="1.0" encoding="UTF-8"?>

<phpunit bootstrap="vendor/autoload.php"
         colors="true"
         convertErrorsToExceptions="true"
         convertNoticesToExceptions="true"
         convertWarningsToExceptions="true"
>
    <testsuites>
        <testsuite>
            <directory>./tests/</directory>
        </testsuite>
    </testsuites>
    <filter>
        <whitelist>
            <directory>./src/</directory>
        </whitelist>
    </filter>
</phpunit>


code:
<?php

namespace Clue\StreamFilter;

use php_user_filter;
use InvalidArgumentException;
use ReflectionFunction;
use Exception;

/**
 *
 * @internal
 * @see append()
 * @see prepend()
 */
class CallbackFilter extends php_user_filter
{
    private $callback;
    private $closed = true;
    private $supportsClose = false;

    public function onCreate()
    {
        $this->closed = false;

        if (!is_callable($this->params)) {
            throw new InvalidArgumentException('No valid callback parameter given to stream_filter_(append|prepend)');
        }
        $this->callback = $this->params;

        // callback supports end event if it accepts invocation without arguments
        $ref = new ReflectionFunction($this->callback);
        $this->supportsClose = ($ref->getNumberOfRequiredParameters() === 0);

        return true;
    }

    public function onClose()
    {
        $this->closed = true;

        // callback supports closing and is not already closed
        if ($this->supportsClose) {
            $this->supportsClose = false;
            // invoke without argument to signal end and discard resulting buffer
            try {
                call_user_func($this->callback);
            } catch (Exception $ignored) {
                // this might be called during engine shutdown, so it's not safe
                // to raise any errors or exceptions here
                // trigger_error('Error closing filter: ' . $ignored->getMessage(), E_USER_WARNING);
            }
        }

        $this->callback = null;
    }

    public function filter($in, $out, &$consumed, $closing)
    {
        // concatenate whole buffer from input brigade
        $data = '';
        while ($bucket = stream_bucket_make_writeable($in)) {
            $consumed += $bucket->datalen;
            $data .= $bucket->data;
        }

        // skip processing callback that already ended
        if ($this->closed) {
            return PSFS_FEED_ME;
        }

        // only invoke filter function if buffer is not empty
        // this may skip flushing a closing filter
        if ($data !== '') {
            try {
                $data = call_user_func($this->callback, $data);
            } catch (Exception $e) {
                // exception should mark filter as closed
                $this->onClose();
                trigger_error('Error invoking filter: ' . $e->getMessage(), E_USER_WARNING);

                return PSFS_ERR_FATAL;
            }
        }

        // mark filter as closed after processing closing chunk
        if ($closing) {
            $this->closed = true;

            // callback supports closing and is not already closed
            if ($this->supportsClose) {
                $this->supportsClose = false;

                // invoke without argument to signal end and append resulting buffer
                try {
                    $data .= call_user_func($this->callback);
                } catch (Exception $e) {
                    trigger_error('Error ending filter: ' . $e->getMessage(), E_USER_WARNING);

                    return PSFS_ERR_FATAL;
                }
            }
        }

        if ($data !== '') {
            // create a new bucket for writing the resulting buffer to the output brigade
            // reusing an existing bucket turned out to be bugged in some environments (ancient PHP versions and HHVM)
            $bucket = @stream_bucket_new($this->stream, $data);

            // legacy PHP versions (PHP < 5.4) do not support passing data from the event signal handler
            // because closing the stream invalidates the stream and its stream bucket brigade before
            // invoking the filter close handler.
            if ($bucket !== false) {
                stream_bucket_append($out, $bucket);
            }
        }

        return PSFS_PASS_ON;
    }
}