Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 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/CookieJar.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
 * This class provides access to service cookies and handles parsing of response
32
 * headers to pull out cookie values.
33
 *
34
 * @class    CAS_CookieJar
35
 * @category Authentication
36
 * @package  PhpCAS
37
 * @author   Adam Franco <afranco@middlebury.edu>
38
 * @license  http://www.apache.org/licenses/LICENSE-2.0  Apache License 2.0
39
 * @link     https://wiki.jasig.org/display/CASC/phpCAS
40
 */
41
class CAS_CookieJar
42
{
43
 
44
    private $_cookies;
45
 
46
    /**
47
     * Create a new cookie jar by passing it a reference to an array in which it
48
     * should store cookies.
49
     *
50
     * @param array &$storageArray Array to store cookies
51
     *
52
     * @return void
53
     */
54
    public function __construct (array &$storageArray)
55
    {
56
        $this->_cookies =& $storageArray;
57
    }
58
 
59
    /**
60
     * Store cookies for a web service request.
61
     * Cookie storage is based on RFC 2965: http://www.ietf.org/rfc/rfc2965.txt
62
     *
63
     * @param string $request_url      The URL that generated the response headers.
64
     * @param array  $response_headers An array of the HTTP response header strings.
65
     *
66
     * @return void
67
     *
68
     * @access private
69
     */
70
    public function storeCookies ($request_url, $response_headers)
71
    {
72
        $urlParts = parse_url($request_url);
73
        $defaultDomain = $urlParts['host'];
74
 
75
        $cookies = $this->parseCookieHeaders($response_headers, $defaultDomain);
76
 
77
        foreach ($cookies as $cookie) {
78
            // Enforce the same-origin policy by verifying that the cookie
79
            // would match the url that is setting it
80
            if (!$this->cookieMatchesTarget($cookie, $urlParts)) {
81
                continue;
82
            }
83
 
84
            // store the cookie
85
            $this->storeCookie($cookie);
86
 
87
            phpCAS::trace($cookie['name'].' -> '.$cookie['value']);
88
        }
89
    }
90
 
91
    /**
92
     * Retrieve cookies applicable for a web service request.
93
     * Cookie applicability is based on RFC 2965: http://www.ietf.org/rfc/rfc2965.txt
94
     *
95
     * @param string $request_url The url that the cookies will be for.
96
     *
97
     * @return array An array containing cookies. E.g. array('name' => 'val');
98
     *
99
     * @access private
100
     */
101
    public function getCookies ($request_url)
102
    {
103
        if (!count($this->_cookies)) {
104
            return array();
105
        }
106
 
107
        // If our request URL can't be parsed, no cookies apply.
108
        $target = parse_url($request_url);
109
        if ($target === false) {
110
            return array();
111
        }
112
 
113
        $this->expireCookies();
114
 
115
        $matching_cookies = array();
116
        foreach ($this->_cookies as $key => $cookie) {
117
            if ($this->cookieMatchesTarget($cookie, $target)) {
118
                $matching_cookies[$cookie['name']] = $cookie['value'];
119
            }
120
        }
121
        return $matching_cookies;
122
    }
123
 
124
 
125
    /**
126
     * Parse Cookies without PECL
127
     * From the comments in http://php.net/manual/en/function.http-parse-cookie.php
128
     *
129
     * @param array  $header        array of header lines.
130
     * @param string $defaultDomain The domain to use if none is specified in
131
     * the cookie.
132
     *
133
     * @return array of cookies
134
     */
135
    protected function parseCookieHeaders( $header, $defaultDomain )
136
    {
137
        phpCAS::traceBegin();
138
        $cookies = array();
139
        foreach ( $header as $line ) {
140
            if ( preg_match('/^Set-Cookie2?: /i', $line)) {
141
                $cookies[] = $this->parseCookieHeader($line, $defaultDomain);
142
            }
143
        }
144
 
145
        phpCAS::traceEnd($cookies);
146
        return $cookies;
147
    }
148
 
149
    /**
150
     * Parse a single cookie header line.
151
     *
152
     * Based on RFC2965 http://www.ietf.org/rfc/rfc2965.txt
153
     *
154
     * @param string $line          The header line.
155
     * @param string $defaultDomain The domain to use if none is specified in
156
     * the cookie.
157
     *
158
     * @return array
159
     */
160
    protected function parseCookieHeader ($line, $defaultDomain)
161
    {
162
        if (!$defaultDomain) {
163
            throw new CAS_InvalidArgumentException(
164
                '$defaultDomain was not provided.'
165
            );
166
        }
167
 
168
        // Set our default values
169
        $cookie = array(
170
            'domain' => $defaultDomain,
171
            'path' => '/',
172
            'secure' => false,
173
        );
174
 
175
        $line = preg_replace('/^Set-Cookie2?: /i', '', trim($line));
176
 
177
        // trim any trailing semicolons.
178
        $line = trim($line, ';');
179
 
180
        phpCAS::trace("Cookie Line: $line");
181
 
182
        // This implementation makes the assumption that semicolons will not
183
        // be present in quoted attribute values. While attribute values that
184
        // contain semicolons are allowed by RFC2965, they are hopefully rare
185
        // enough to ignore for our purposes. Most browsers make the same
186
        // assumption.
187
        $attributeStrings = explode(';', $line);
188
 
189
        foreach ( $attributeStrings as $attributeString ) {
190
            // split on the first equals sign and use the rest as value
191
            $attributeParts = explode('=', $attributeString, 2);
192
 
193
            $attributeName = trim($attributeParts[0]);
194
            $attributeNameLC = strtolower($attributeName);
195
 
196
            if (isset($attributeParts[1])) {
197
                $attributeValue = trim($attributeParts[1]);
198
                // Values may be quoted strings.
199
                if (strpos($attributeValue, '"') === 0) {
200
                    $attributeValue = trim($attributeValue, '"');
201
                    // unescape any escaped quotes:
202
                    $attributeValue = str_replace('\"', '"', $attributeValue);
203
                }
204
            } else {
205
                $attributeValue = null;
206
            }
207
 
208
            switch ($attributeNameLC) {
209
            case 'expires':
210
                $cookie['expires'] = strtotime($attributeValue);
211
                break;
212
            case 'max-age':
213
                $cookie['max-age'] = (int)$attributeValue;
214
                // Set an expiry time based on the max-age
215
                if ($cookie['max-age']) {
216
                    $cookie['expires'] = time() + $cookie['max-age'];
217
                } else {
218
                    // If max-age is zero, then the cookie should be removed
219
                    // imediately so set an expiry before now.
220
                    $cookie['expires'] = time() - 1;
221
                }
222
                break;
223
            case 'secure':
224
                $cookie['secure'] = true;
225
                break;
226
            case 'domain':
227
            case 'path':
228
            case 'port':
229
            case 'version':
230
            case 'comment':
231
            case 'commenturl':
232
            case 'discard':
233
            case 'httponly':
234
            case 'samesite':
235
                $cookie[$attributeNameLC] = $attributeValue;
236
                break;
237
            default:
238
                $cookie['name'] = $attributeName;
239
                $cookie['value'] = $attributeValue;
240
            }
241
        }
242
 
243
        return $cookie;
244
    }
245
 
246
    /**
247
     * Add, update, or remove a cookie.
248
     *
249
     * @param array $cookie A cookie array as created by parseCookieHeaders()
250
     *
251
     * @return void
252
     *
253
     * @access protected
254
     */
255
    protected function storeCookie ($cookie)
256
    {
257
        // Discard any old versions of this cookie.
258
        $this->discardCookie($cookie);
259
        $this->_cookies[] = $cookie;
260
 
261
    }
262
 
263
    /**
264
     * Discard an existing cookie
265
     *
266
     * @param array $cookie An cookie
267
     *
268
     * @return void
269
     *
270
     * @access protected
271
     */
272
    protected function discardCookie ($cookie)
273
    {
274
        if (!isset($cookie['domain'])
275
            || !isset($cookie['path'])
276
            || !isset($cookie['path'])
277
        ) {
278
            throw new CAS_InvalidArgumentException('Invalid Cookie array passed.');
279
        }
280
 
281
        foreach ($this->_cookies as $key => $old_cookie) {
282
            if ( $cookie['domain'] == $old_cookie['domain']
283
                && $cookie['path'] == $old_cookie['path']
284
                && $cookie['name'] == $old_cookie['name']
285
            ) {
286
                unset($this->_cookies[$key]);
287
            }
288
        }
289
    }
290
 
291
    /**
292
     * Go through our stored cookies and remove any that are expired.
293
     *
294
     * @return void
295
     *
296
     * @access protected
297
     */
298
    protected function expireCookies ()
299
    {
300
        foreach ($this->_cookies as $key => $cookie) {
301
            if (isset($cookie['expires']) && $cookie['expires'] < time()) {
302
                unset($this->_cookies[$key]);
303
            }
304
        }
305
    }
306
 
307
    /**
308
     * Answer true if cookie is applicable to a target.
309
     *
310
     * @param array $cookie An array of cookie attributes.
311
     * @param array|false $target An array of URL attributes as generated by parse_url().
312
     *
313
     * @return bool
314
     *
315
     * @access private
316
     */
317
    protected function cookieMatchesTarget ($cookie, $target)
318
    {
319
        if (!is_array($target)) {
320
            throw new CAS_InvalidArgumentException(
321
                '$target must be an array of URL attributes as generated by parse_url().'
322
            );
323
        }
324
        if (!isset($target['host'])) {
325
            throw new CAS_InvalidArgumentException(
326
                '$target must be an array of URL attributes as generated by parse_url().'
327
            );
328
        }
329
 
330
        // Verify that the scheme matches
331
        if ($cookie['secure'] && $target['scheme'] != 'https') {
332
            return false;
333
        }
334
 
335
        // Verify that the host matches
336
        // Match domain and mulit-host cookies
337
        if (strpos($cookie['domain'], '.') === 0) {
338
            // .host.domain.edu cookies are valid for host.domain.edu
339
            if (substr($cookie['domain'], 1) == $target['host']) {
340
                // continue with other checks
341
            } else {
342
                // non-exact host-name matches.
343
                // check that the target host a.b.c.edu is within .b.c.edu
344
                $pos = strripos($target['host'], $cookie['domain']);
345
                if (!$pos) {
346
                    return false;
347
                }
348
                // verify that the cookie domain is the last part of the host.
349
                if ($pos + strlen($cookie['domain']) != strlen($target['host'])) {
350
                    return false;
351
                }
352
                // verify that the host name does not contain interior dots as per
353
                // RFC 2965 section 3.3.2  Rejecting Cookies
354
                // http://www.ietf.org/rfc/rfc2965.txt
355
                $hostname = substr($target['host'], 0, $pos);
356
                if (strpos($hostname, '.') !== false) {
357
                    return false;
358
                }
359
            }
360
        } else {
361
            // If the cookie host doesn't begin with '.',
362
            // the host must case-insensitive match exactly
363
            if (strcasecmp($target['host'], $cookie['domain']) !== 0) {
364
                return false;
365
            }
366
        }
367
 
368
        // Verify that the port matches
369
        if (isset($cookie['ports'])
370
            && !in_array($target['port'], $cookie['ports'])
371
        ) {
372
            return false;
373
        }
374
 
375
        // Verify that the path matches
376
        if (strpos($target['path'], $cookie['path']) !== 0) {
377
            return false;
378
        }
379
 
380
        return true;
381
    }
382
 
383
}
384
 
385
?>