Completed
Push — develop ( a2c65d...283ac2 )
by Kristijan
13s
created

Response::addHttpHeaders()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 3
rs 10
c 0
b 0
f 0
cc 1
eloc 1
nc 1
nop 1
1
<?php
2
3
namespace OAuth2;
4
5
/**
6
 * Class to handle OAuth2 Responses in a graceful way.  Use this interface
7
 * to output the proper OAuth2 responses.
8
 *
9
 * @see OAuth2\ResponseInterface
10
 *
11
 * This class borrows heavily from the Symfony2 Framework and is part of the symfony package
12
 * @see Symfony\Component\HttpFoundation\Request (https://github.com/symfony/symfony)
13
 */
14
class Response implements ResponseInterface
15
{
16
    public $version;
17
    protected $statusCode = 200;
18
    protected $statusText;
19
    protected $parameters = array();
20
    protected $httpHeaders = array();
21
22
    public static $statusTexts = array(
23
        100 => 'Continue',
24
        101 => 'Switching Protocols',
25
        200 => 'OK',
26
        201 => 'Created',
27
        202 => 'Accepted',
28
        203 => 'Non-Authoritative Information',
29
        204 => 'No Content',
30
        205 => 'Reset Content',
31
        206 => 'Partial Content',
32
        300 => 'Multiple Choices',
33
        301 => 'Moved Permanently',
34
        302 => 'Found',
35
        303 => 'See Other',
36
        304 => 'Not Modified',
37
        305 => 'Use Proxy',
38
        307 => 'Temporary Redirect',
39
        400 => 'Bad Request',
40
        401 => 'Unauthorized',
41
        402 => 'Payment Required',
42
        403 => 'Forbidden',
43
        404 => 'Not Found',
44
        405 => 'Method Not Allowed',
45
        406 => 'Not Acceptable',
46
        407 => 'Proxy Authentication Required',
47
        408 => 'Request Timeout',
48
        409 => 'Conflict',
49
        410 => 'Gone',
50
        411 => 'Length Required',
51
        412 => 'Precondition Failed',
52
        413 => 'Request Entity Too Large',
53
        414 => 'Request-URI Too Long',
54
        415 => 'Unsupported Media Type',
55
        416 => 'Requested Range Not Satisfiable',
56
        417 => 'Expectation Failed',
57
        418 => 'I\'m a teapot',
58
        500 => 'Internal Server Error',
59
        501 => 'Not Implemented',
60
        502 => 'Bad Gateway',
61
        503 => 'Service Unavailable',
62
        504 => 'Gateway Timeout',
63
        505 => 'HTTP Version Not Supported',
64
    );
65
66
    public function __construct($parameters = array(), $statusCode = 200, $headers = array())
67
    {
68
        $this->setParameters($parameters);
69
        $this->setStatusCode($statusCode);
70
        $this->setHttpHeaders($headers);
71
        $this->version = '1.1';
72
    }
73
74
    /**
75
     * Converts the response object to string containing all headers and the response content.
76
     *
77
     * @return string The response with headers and content
78
     */
79
    public function __toString()
80
    {
81
        $headers = array();
82
        foreach ($this->httpHeaders as $name => $value) {
83
            $headers[$name] = (array) $value;
84
        }
85
86
        return
87
            sprintf('HTTP/%s %s %s', $this->version, $this->statusCode, $this->statusText)."\r\n".
88
            $this->getHttpHeadersAsString($headers)."\r\n".
89
            $this->getResponseBody();
90
    }
91
92
    /**
93
     * Returns the build header line.
94
     *
95
     * @param string $name  The header name
96
     * @param string $value The header value
97
     *
98
     * @return string The built header line
99
     */
100
    protected function buildHeader($name, $value)
101
    {
102
        return sprintf("%s: %s\n", $name, $value);
103
    }
104
105
    public function getStatusCode()
106
    {
107
        return $this->statusCode;
108
    }
109
110
    public function setStatusCode($statusCode, $text = null)
111
    {
112
        $this->statusCode = (int) $statusCode;
113
        if ($this->isInvalid()) {
114
            throw new \InvalidArgumentException(sprintf('The HTTP status code "%s" is not valid.', $statusCode));
115
        }
116
117
        $this->statusText = false === $text ? '' : (null === $text ? self::$statusTexts[$this->statusCode] : $text);
118
    }
119
120
    public function getStatusText()
121
    {
122
        return $this->statusText;
123
    }
124
125
    public function getParameters()
126
    {
127
        return $this->parameters;
128
    }
129
130
    public function setParameters(array $parameters)
131
    {
132
        $this->parameters = $parameters;
133
    }
134
135
    public function addParameters(array $parameters)
136
    {
137
        $this->parameters = array_merge($this->parameters, $parameters);
138
    }
139
140
    public function getParameter($name, $default = null)
141
    {
142
        return isset($this->parameters[$name]) ? $this->parameters[$name] : $default;
143
    }
144
145
    public function setParameter($name, $value)
146
    {
147
        $this->parameters[$name] = $value;
148
    }
149
150
    public function setHttpHeaders(array $httpHeaders)
151
    {
152
        $this->httpHeaders = $httpHeaders;
153
    }
154
155
    public function setHttpHeader($name, $value)
156
    {
157
        $this->httpHeaders[$name] = $value;
158
    }
159
160
    public function addHttpHeaders(array $httpHeaders)
161
    {
162
        $this->httpHeaders = array_merge($this->httpHeaders, $httpHeaders);
163
    }
164
165
    public function getHttpHeaders()
166
    {
167
        return $this->httpHeaders;
168
    }
169
170
    public function getHttpHeader($name, $default = null)
171
    {
172
        return isset($this->httpHeaders[$name]) ? $this->httpHeaders[$name] : $default;
173
    }
174
175
    public function getResponseBody($format = 'json')
176
    {
177
        switch ($format) {
178
            case 'json':
179
                return json_encode($this->parameters);
180
            case 'xml':
181
                // this only works for single-level arrays
182
                $xml = new \SimpleXMLElement('<response/>');
183
                foreach ($this->parameters as $key => $param) {
184
                    $xml->addChild($key, $param);
185
                }
186
187
                return $xml->asXML();
188
        }
189
190
        throw new \InvalidArgumentException(sprintf('The format %s is not supported', $format));
191
192
    }
193
194
    public function send($format = 'json')
195
    {
196
        // headers have already been sent by the developer
197
        if (headers_sent()) {
198
            return;
199
        }
200
201
        switch ($format) {
202
            case 'json':
203
                $this->setHttpHeader('Content-Type', 'application/json');
204
                break;
205
            case 'xml':
206
                $this->setHttpHeader('Content-Type', 'text/xml');
207
                break;
208
        }
209
        // status
210
        header(sprintf('HTTP/%s %s %s', $this->version, $this->statusCode, $this->statusText));
211
212
        foreach ($this->getHttpHeaders() as $name => $header) {
213
            header(sprintf('%s: %s', $name, $header));
214
        }
215
        echo $this->getResponseBody($format);
216
    }
217
218
    public function setError($statusCode, $error, $errorDescription = null, $errorUri = null)
219
    {
220
        $parameters = array(
221
            'error' => $error,
222
            'error_description' => $errorDescription,
223
        );
224
225
        if (!is_null($errorUri)) {
226
            if (strlen($errorUri) > 0 && $errorUri[0] == '#') {
227
                // we are referencing an oauth bookmark (for brevity)
228
                $errorUri = 'http://tools.ietf.org/html/rfc6749' . $errorUri;
229
            }
230
            $parameters['error_uri'] = $errorUri;
231
        }
232
233
        $httpHeaders = array(
234
            'Cache-Control' => 'no-store'
235
        );
236
237
        $this->setStatusCode($statusCode);
238
        $this->addParameters($parameters);
239
        $this->addHttpHeaders($httpHeaders);
240
241
        if (!$this->isClientError() && !$this->isServerError()) {
242
            throw new \InvalidArgumentException(sprintf('The HTTP status code is not an error ("%s" given).', $statusCode));
243
        }
244
    }
245
246
    public function setRedirect($statusCode, $url, $state = null, $error = null, $errorDescription = null, $errorUri = null)
247
    {
248
        if (empty($url)) {
249
            throw new \InvalidArgumentException('Cannot redirect to an empty URL.');
250
        }
251
252
        $parameters = array();
253
254
        if (!is_null($state)) {
255
            $parameters['state'] = $state;
256
        }
257
258
        if (!is_null($error)) {
259
            $this->setError(400, $error, $errorDescription, $errorUri);
260
        }
261
        $this->setStatusCode($statusCode);
262
        $this->addParameters($parameters);
263
264
        if (count($this->parameters) > 0) {
265
            // add parameters to URL redirection
266
            $parts = parse_url($url);
267
            $sep = isset($parts['query']) && count($parts['query']) > 0 ? '&' : '?';
268
            $url .= $sep . http_build_query($this->parameters);
269
        }
270
271
        $this->addHttpHeaders(array('Location' =>  $url));
272
273
        if (!$this->isRedirection()) {
274
            throw new \InvalidArgumentException(sprintf('The HTTP status code is not a redirect ("%s" given).', $statusCode));
275
        }
276
    }
277
278
    // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
279
    /**
280
     * @return Boolean
281
     *
282
     * @api
283
     */
284
    public function isInvalid()
285
    {
286
        return $this->statusCode < 100 || $this->statusCode >= 600;
287
    }
288
289
    /**
290
     * @return Boolean
291
     *
292
     * @api
293
     */
294
    public function isInformational()
295
    {
296
        return $this->statusCode >= 100 && $this->statusCode < 200;
297
    }
298
299
    /**
300
     * @return Boolean
301
     *
302
     * @api
303
     */
304
    public function isSuccessful()
305
    {
306
        return $this->statusCode >= 200 && $this->statusCode < 300;
307
    }
308
309
    /**
310
     * @return Boolean
311
     *
312
     * @api
313
     */
314
    public function isRedirection()
315
    {
316
        return $this->statusCode >= 300 && $this->statusCode < 400;
317
    }
318
319
    /**
320
     * @return Boolean
321
     *
322
     * @api
323
     */
324
    public function isClientError()
325
    {
326
        return $this->statusCode >= 400 && $this->statusCode < 500;
327
    }
328
329
    /**
330
     * @return Boolean
331
     *
332
     * @api
333
     */
334
    public function isServerError()
335
    {
336
        return $this->statusCode >= 500 && $this->statusCode < 600;
337
    }
338
339
    /*
340
     * Functions from Symfony2 HttpFoundation - output pretty header
341
     */
342
    private function getHttpHeadersAsString($headers)
343
    {
344
        if (count($headers) == 0) {
345
            return '';
346
        }
347
348
        $max = max(array_map('strlen', array_keys($headers))) + 1;
349
        $content = '';
350
        ksort($headers);
351
        foreach ($headers as $name => $values) {
352
            foreach ($values as $value) {
353
                $content .= sprintf("%-{$max}s %s\r\n", $this->beautifyHeaderName($name).':', $value);
354
            }
355
        }
356
357
        return $content;
358
    }
359
360
    private function beautifyHeaderName($name)
361
    {
362
        return preg_replace_callback('/\-(.)/', array($this, 'beautifyCallback'), ucfirst($name));
363
    }
364
365
    private function beautifyCallback($match)
366
    {
367
        return '-'.strtoupper($match[1]);
368
    }
369
}
370