1<?php
2
3/**
4 * Licensed to Jasig under one or more contributor license
5 * agreements. See the NOTICE file distributed with this work for
6 * additional information regarding copyright ownership.
7 *
8 * Jasig licenses this file to you under the Apache License,
9 * Version 2.0 (the "License"); you may not use this file except in
10 * compliance with the License. You may obtain a copy of the License at:
11 *
12 * http://www.apache.org/licenses/LICENSE-2.0
13 *
14 * Unless required by applicable law or agreed to in writing, software
15 * distributed under the License is distributed on an "AS IS" BASIS,
16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 * See the License for the specific language governing permissions and
18 * limitations under the License.
19 *
20 * PHP Version 7
21 *
22 * @file     CAS/Request/AbstractRequest.php
23 * @category Authentication
24 * @package  PhpCAS
25 * @author   Adam Franco <afranco@middlebury.edu>
26 * @license  http://www.apache.org/licenses/LICENSE-2.0  Apache License 2.0
27 * @link     https://wiki.jasig.org/display/CASC/phpCAS
28 */
29
30/**
31 * Provides support for performing web-requests via curl
32 *
33 * @class    CAS_Request_AbstractRequest
34 * @category Authentication
35 * @package  PhpCAS
36 * @author   Adam Franco <afranco@middlebury.edu>
37 * @license  http://www.apache.org/licenses/LICENSE-2.0  Apache License 2.0
38 * @link     https://wiki.jasig.org/display/CASC/phpCAS
39 */
40abstract class CAS_Request_AbstractRequest
41implements CAS_Request_RequestInterface
42{
43
44    protected $url = null;
45    protected $cookies = array();
46    protected $headers = array();
47    protected $isPost = false;
48    protected $postBody = null;
49    protected $caCertPath = null;
50    protected $validateCN = true;
51    private $_sent = false;
52    private $_responseHeaders = array();
53    private $_responseBody = null;
54    private $_errorMessage = '';
55
56    /*********************************************************
57     * Configure the Request
58    *********************************************************/
59
60    /**
61     * Set the URL of the Request
62     *
63     * @param string $url Url to set
64     *
65     * @return void
66     * @throws CAS_OutOfSequenceException If called after the Request has been sent.
67     */
68    public function setUrl ($url)
69    {
70        if ($this->_sent) {
71            throw new CAS_OutOfSequenceException(
72                'Request has already been sent cannot '.__METHOD__
73            );
74        }
75
76        $this->url = $url;
77    }
78
79    /**
80     * Add a cookie to the request.
81     *
82     * @param string $name  Name of entry
83     * @param string $value value of entry
84     *
85     * @return void
86     * @throws CAS_OutOfSequenceException If called after the Request has been sent.
87     */
88    public function addCookie ($name, $value)
89    {
90        if ($this->_sent) {
91            throw new CAS_OutOfSequenceException(
92                'Request has already been sent cannot '.__METHOD__
93            );
94        }
95
96        $this->cookies[$name] = $value;
97    }
98
99    /**
100     * Add an array of cookies to the request.
101     * The cookie array is of the form
102     *     array('cookie_name' => 'cookie_value', 'cookie_name2' => cookie_value2')
103     *
104     * @param array $cookies cookies to add
105     *
106     * @return void
107     * @throws CAS_OutOfSequenceException If called after the Request has been sent.
108     */
109    public function addCookies (array $cookies)
110    {
111        if ($this->_sent) {
112            throw new CAS_OutOfSequenceException(
113                'Request has already been sent cannot '.__METHOD__
114            );
115        }
116
117        $this->cookies = array_merge($this->cookies, $cookies);
118    }
119
120    /**
121     * Add a header string to the request.
122     *
123     * @param string $header Header to add
124     *
125     * @return void
126     * @throws CAS_OutOfSequenceException If called after the Request has been sent.
127     */
128    public function addHeader ($header)
129    {
130        if ($this->_sent) {
131            throw new CAS_OutOfSequenceException(
132                'Request has already been sent cannot '.__METHOD__
133            );
134        }
135
136        $this->headers[] = $header;
137    }
138
139    /**
140     * Add an array of header strings to the request.
141     *
142     * @param array $headers headers to add
143     *
144     * @return void
145     * @throws CAS_OutOfSequenceException If called after the Request has been sent.
146     */
147    public function addHeaders (array $headers)
148    {
149        if ($this->_sent) {
150            throw new CAS_OutOfSequenceException(
151                'Request has already been sent cannot '.__METHOD__
152            );
153        }
154
155        $this->headers = array_merge($this->headers, $headers);
156    }
157
158    /**
159     * Make the request a POST request rather than the default GET request.
160     *
161     * @return void
162     * @throws CAS_OutOfSequenceException If called after the Request has been sent.
163     */
164    public function makePost ()
165    {
166        if ($this->_sent) {
167            throw new CAS_OutOfSequenceException(
168                'Request has already been sent cannot '.__METHOD__
169            );
170        }
171
172        $this->isPost = true;
173    }
174
175    /**
176     * Add a POST body to the request
177     *
178     * @param string $body body to add
179     *
180     * @return void
181     * @throws CAS_OutOfSequenceException If called after the Request has been sent.
182     */
183    public function setPostBody ($body)
184    {
185        if ($this->_sent) {
186            throw new CAS_OutOfSequenceException(
187                'Request has already been sent cannot '.__METHOD__
188            );
189        }
190        if (!$this->isPost) {
191            throw new CAS_OutOfSequenceException(
192                'Cannot add a POST body to a GET request, use makePost() first.'
193            );
194        }
195
196        $this->postBody = $body;
197    }
198
199    /**
200     * Specify the path to an SSL CA certificate to validate the server with.
201     *
202     * @param string $caCertPath  path to cert
203     * @param bool   $validate_cn valdiate CN of certificate
204     *
205     * @return void
206     * @throws CAS_OutOfSequenceException If called after the Request has been sent.
207     */
208    public function setSslCaCert ($caCertPath,$validate_cn=true)
209    {
210        if ($this->_sent) {
211            throw new CAS_OutOfSequenceException(
212                'Request has already been sent cannot '.__METHOD__
213            );
214        }
215        $this->caCertPath = $caCertPath;
216        $this->validateCN = $validate_cn;
217    }
218
219    /*********************************************************
220     * 2. Send the Request
221    *********************************************************/
222
223    /**
224     * Perform the request.
225     *
226     * @return bool TRUE on success, FALSE on failure.
227     * @throws CAS_OutOfSequenceException If called multiple times.
228     */
229    public function send ()
230    {
231        if ($this->_sent) {
232            throw new CAS_OutOfSequenceException(
233                'Request has already been sent cannot send again.'
234            );
235        }
236        if (is_null($this->url) || !$this->url) {
237            throw new CAS_OutOfSequenceException(
238                'A url must be specified via setUrl() before the request can be sent.'
239            );
240        }
241        $this->_sent = true;
242        return $this->sendRequest();
243    }
244
245    /**
246     * Send the request and store the results.
247     *
248     * @return bool TRUE on success, FALSE on failure.
249     */
250    abstract protected function sendRequest ();
251
252    /**
253     * Store the response headers.
254     *
255     * @param array $headers headers to store
256     *
257     * @return void
258     */
259    protected function storeResponseHeaders (array $headers)
260    {
261        $this->_responseHeaders = array_merge($this->_responseHeaders, $headers);
262    }
263
264    /**
265     * Store a single response header to our array.
266     *
267     * @param string $header header to store
268     *
269     * @return void
270     */
271    protected function storeResponseHeader ($header)
272    {
273        $this->_responseHeaders[] = $header;
274    }
275
276    /**
277     * Store the response body.
278     *
279     * @param string $body body to store
280     *
281     * @return void
282     */
283    protected function storeResponseBody ($body)
284    {
285        $this->_responseBody = $body;
286    }
287
288    /**
289     * Add a string to our error message.
290     *
291     * @param string $message message to add
292     *
293     * @return void
294     */
295    protected function storeErrorMessage ($message)
296    {
297        $this->_errorMessage .= $message;
298    }
299
300    /*********************************************************
301     * 3. Access the response
302    *********************************************************/
303
304    /**
305     * Answer the headers of the response.
306     *
307     * @return array An array of header strings.
308     * @throws CAS_OutOfSequenceException If called before the Request has been sent.
309     */
310    public function getResponseHeaders ()
311    {
312        if (!$this->_sent) {
313            throw new CAS_OutOfSequenceException(
314                'Request has not been sent yet. Cannot '.__METHOD__
315            );
316        }
317        return $this->_responseHeaders;
318    }
319
320    /**
321     * Answer HTTP status code of the response
322     *
323     * @return int
324     * @throws CAS_OutOfSequenceException If called before the Request has been sent.
325     * @throws CAS_Request_Exception if the response did not contain a status code
326     */
327    public function getResponseStatusCode ()
328    {
329        if (!$this->_sent) {
330            throw new CAS_OutOfSequenceException(
331                'Request has not been sent yet. Cannot '.__METHOD__
332            );
333        }
334
335        if (!preg_match(
336            '/HTTP\/[0-9.]+\s+([0-9]+)\s*(.*)/',
337            $this->_responseHeaders[0], $matches
338        )
339        ) {
340            throw new CAS_Request_Exception(
341                'Bad response, no status code was found in the first line.'
342            );
343        }
344
345        return intval($matches[1]);
346    }
347
348    /**
349     * Answer the body of response.
350     *
351     * @return string
352     * @throws CAS_OutOfSequenceException If called before the Request has been sent.
353     */
354    public function getResponseBody ()
355    {
356        if (!$this->_sent) {
357            throw new CAS_OutOfSequenceException(
358                'Request has not been sent yet. Cannot '.__METHOD__
359            );
360        }
361
362        return $this->_responseBody;
363    }
364
365    /**
366     * Answer a message describing any errors if the request failed.
367     *
368     * @return string
369     * @throws CAS_OutOfSequenceException If called before the Request has been sent.
370     */
371    public function getErrorMessage ()
372    {
373        if (!$this->_sent) {
374            throw new CAS_OutOfSequenceException(
375                'Request has not been sent yet. Cannot '.__METHOD__
376            );
377        }
378        return $this->_errorMessage;
379    }
380}
381