Projet

Général

Profil

Paste
Télécharger (9,76 ko) Statistiques
| Branche: | Révision:

root / htmltest / sites / all / libraries / CAS.dgeo / CAS / CookieJar.php @ 3753f249

1
<?php
2
/*
3
 * Copyright © 2003-2010, The ESUP-Portail consortium & the JA-SIG Collaborative.
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *     * Redistributions of source code must retain the above copyright notice,
10
 *       this list of conditions and the following disclaimer.
11
 *     * Redistributions in binary form must reproduce the above copyright notice,
12
 *       this list of conditions and the following disclaimer in the documentation
13
 *       and/or other materials provided with the distribution.
14
 *     * Neither the name of the ESUP-Portail consortium & the JA-SIG
15
 *       Collaborative nor the names of its contributors may be used to endorse or
16
 *       promote products derived from this software without specific prior
17
 *       written permission.
18

19
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
20
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
23
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
26
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
 */
30

    
31
include_once(dirname(__FILE__).'/InvalidArgumentException.php');
32

    
33
/**
34
 * This class provides access to service cookies and handles parsing of response
35
 * headers to pull out cookie values.
36
 *
37
 */
38
class CAS_CookieJar {
39

    
40
        private $_cookies;
41

    
42
        /**
43
         * Create a new cookie jar by passing it a reference to an array in which it
44
         * should store cookies.
45
         *
46
         * @param ref array $storageArray
47
         * @return void
48
         */
49
        public function __construct (array &$storageArray) {
50
                $this->_cookies =& $storageArray;
51
        }
52

    
53
        /**
54
         * Store cookies for a web service request.
55
         * Cookie storage is based on RFC 2965: http://www.ietf.org/rfc/rfc2965.txt
56
         *
57
         * @param string $request_url The URL that generated the response headers.
58
         * @param array $response_headers An array of the HTTP response header strings.
59
         *
60
         * @return void
61
         *
62
         * @access private
63
         */
64
        public function storeCookies ($request_url, $response_headers) {
65
                $urlParts = parse_url($request_url);
66
                $defaultDomain = $urlParts['host'];
67

    
68
                $cookies = $this->parseCookieHeaders($response_headers, $defaultDomain);
69

    
70
                // var_dump($cookies);
71
                foreach ($cookies as $cookie) {
72
                        // Enforce the same-origin policy by verifying that the cookie
73
                        // would match the url that is setting it
74
                        if (!$this->cookieMatchesTarget($cookie, $urlParts))
75
                                continue;
76

    
77
                        // store the cookie
78
                        $this->storeCookie($cookie);
79

    
80
                        phpCAS::trace($cookie['name'].' -> '.$cookie['value']);
81
                }
82
        }
83

    
84
        /**
85
         * Retrieve cookies applicable for a web service request.
86
         * Cookie applicability is based on RFC 2965: http://www.ietf.org/rfc/rfc2965.txt
87
         *
88
         * @param string $request_url The url that the cookies will be for.
89
         *
90
         * @return array An array containing cookies. E.g. array('name' => 'val');
91
         *
92
         * @access private
93
         */
94
        public function getCookies ($request_url) {
95
                if (!count($this->_cookies))
96
                        return array();
97

    
98
                // If our request URL can't be parsed, no cookies apply.
99
                $target = parse_url($request_url);
100
                if ($target === FALSE)
101
                        return array();
102

    
103
                $this->expireCookies();
104

    
105
                $matching_cookies = array();
106
                foreach ($this->_cookies as $key => $cookie) {
107
                        if ($this->cookieMatchesTarget($cookie, $target)) {
108
                                $matching_cookies[$cookie['name']] = $cookie['value'];
109
                        }
110
                }
111
                return $matching_cookies;
112
        }
113

    
114

    
115
        /**
116
         * Parse Cookies without PECL
117
         * From the comments in http://php.net/manual/en/function.http-parse-cookie.php
118
         * @param array $header         An array of header lines.
119
         * @param string $defaultDomain         The domain to use if none is specified in the cookie.
120
         * @return array of cookies
121
         */
122
        protected function parseCookieHeaders( $header, $defaultDomain ) {
123
                phpCAS::traceBegin();
124
                $cookies = array();
125
                foreach( $header as $line ) {
126
                        if( preg_match( '/^Set-Cookie2?: /i', $line ) ) {
127
                                $cookies[] = $this->parseCookieHeader($line, $defaultDomain);
128
                        }
129
                }
130

    
131
                phpCAS::traceEnd($cookies);
132
                return $cookies;
133
        }
134

    
135
        /**
136
         * Parse a single cookie header line.
137
         *
138
         * Based on RFC2965 http://www.ietf.org/rfc/rfc2965.txt
139
         *
140
         * @param string $line The header line.
141
         * @param string $defaultDomain The domain to use if none is specified in the cookie.
142
         * @return array
143
         */
144
        protected function parseCookieHeader ($line, $defaultDomain) {
145
                if (!$defaultDomain)
146
                        throw new CAS_InvalidArgumentException('$defaultDomain was not provided.');
147

    
148
                // Set our default values
149
                $cookie = array(
150
                        'domain' => $defaultDomain,
151
                        'path' => '/',
152
                        'secure' => false,
153
                );
154

    
155
                $line = preg_replace( '/^Set-Cookie2?: /i', '', trim( $line ) );
156

    
157
                // trim any trailing semicolons.
158
                $line = trim($line, ';');
159

    
160
                phpCAS::trace("Cookie Line: $line");
161

    
162
                // This implementation makes the assumption that semicolons will not
163
                // be present in quoted attribute values. While attribute values that
164
                // contain semicolons are allowed by RFC2965, they are hopefully rare
165
                // enough to ignore for our purposes. Most browsers make the same 
166
                // assumption.
167
                $attributeStrings = explode( ';', $line );
168

    
169
                foreach( $attributeStrings as $attributeString ) {
170
                        // split on the first equals sign and use the rest as value
171
                        $attributeParts = explode( '=', $attributeString, 2);
172

    
173
                        $attributeName = trim($attributeParts[0]);
174
                        $attributeNameLC = strtolower($attributeName);
175

    
176
                        if (isset($attributeParts[1])) {
177
                                $attributeValue = trim($attributeParts[1]);
178
                                // Values may be quoted strings.
179
                                if (strpos($attributeValue, '"') === 0) {
180
                                        $attributeValue = trim($attributeValue, '"');
181
                                        // unescape any escaped quotes:
182
                                        $attributeValue = str_replace('\"', '"', $attributeValue);
183
                                }
184
                        } else {
185
                                $attributeValue = null;
186
                        }
187

    
188
                        switch ($attributeNameLC) {
189
                                case 'expires':
190
                                        $cookie['expires'] = strtotime($attributeValue);
191
                                        break;
192
                                case 'max-age':
193
                                        $cookie['max-age'] = (int)$attributeValue;
194
                                        // Set an expiry time based on the max-age
195
                                        if ($cookie['max-age'])
196
                                                $cookie['expires'] = time() + $cookie['max-age'];
197
                                        // If max-age is zero, then the cookie should be removed imediately
198
                                        // so set an expiry before now.
199
                                        else
200
                                                $cookie['expires'] = time() - 1;
201
                                        break;
202
                                case 'secure':
203
                                        $cookie['secure'] = true;
204
                                        break;
205
                                case 'domain':
206
                                case 'path':
207
                                case 'port':
208
                                case 'version':
209
                                case 'comment':
210
                                case 'commenturl':
211
                                case 'discard':
212
                                case 'httponly':
213
                                        $cookie[$attributeNameLC] = $attributeValue;
214
                                        break;
215
                                default:
216
                                        $cookie['name'] = $attributeName;
217
                                        $cookie['value'] = $attributeValue;
218
                        }
219
                }
220

    
221
                return $cookie;
222
        }
223

    
224
        /**
225
         * Add, update, or remove a cookie.
226
         *
227
         * @param array $cookie A cookie array as created by parseCookieHeaders()
228
         *
229
         * @return void
230
         *
231
         * @access private
232
         */
233
        protected function storeCookie ($cookie) {
234
                // Discard any old versions of this cookie.
235
                $this->discardCookie($cookie);
236
                $this->_cookies[] = $cookie;
237

    
238
        }
239

    
240
        /**
241
         * Discard an existing cookie
242
         *
243
         * @param stdClass $cookie
244
         *
245
         * @return void
246
         *
247
         * @access private
248
         */
249
        protected function discardCookie ($cookie) {
250
                if (!isset($cookie['domain']) || !isset($cookie['path']) || !isset($cookie['path']))
251
                        throw new CAS_InvalidArgumentException('Invalid Cookie array passed.');
252

    
253
                foreach ($this->_cookies as $key => $old_cookie) {
254
                        if ($cookie['domain'] == $old_cookie['domain']
255
                        && $cookie['path'] == $old_cookie['path']
256
                        && $cookie['name'] == $old_cookie['name'])
257
                        {
258
                                unset($this->_cookies[$key]);
259
                        }
260
                }
261
        }
262

    
263
        /**
264
         * Go through our stored cookies and remove any that are expired.
265
         *
266
         * @return void
267
         *
268
         * @access private
269
         */
270
        protected function expireCookies () {
271
                foreach ($this->_cookies as $key => $cookie) {
272
                        if (isset($cookie['expires']) && $cookie['expires'] < time()) {
273
                                unset($this->_cookies[$key]);
274
                        }
275
                }
276
        }
277

    
278
        /**
279
         * Answer true if cookie is applicable to a target.
280
         *
281
         * @param array $cookie An array of cookie attributes.
282
         * @param array $target An array of URL attributes as generated by parse_url().
283
         *
284
         * @return boolean
285
         *
286
         * @access private
287
         */
288
        protected function cookieMatchesTarget ($cookie, $target) {
289
                if (!is_array($target))
290
                        throw new CAS_InvalidArgumentException('$target must be an array of URL attributes as generated by parse_url().');
291
                if (!isset($target['host']))
292
                        throw new CAS_InvalidArgumentException('$target must be an array of URL attributes as generated by parse_url().');
293

    
294
                // Verify that the scheme matches
295
                if ($cookie['secure'] && $target['scheme'] != 'https')
296
                return false;
297
                
298
                // Verify that the host matches
299
                // Match domain and mulit-host cookies
300
                if (strpos($cookie['domain'], '.') === 0) {
301
                        // check that the target host a.b.c.edu is within .b.c.edu
302
                        $pos = strripos($target['host'], $cookie['domain']);
303
                        if (!$pos)
304
                        return false;
305
                        // verify that the cookie domain is the last part of the host.
306
                        if ($pos + strlen($cookie['domain']) != strlen($target['host']))
307
                        return false;
308
                }
309
                // If the cookie host doesn't begin with '.', the host must case-insensitive
310
                // match exactly
311
                else {
312
                        if (strcasecmp($target['host'], $cookie['domain']) !== 0)
313
                        return false;
314
                }
315

    
316
                // Verify that the port matches
317
                if (isset($cookie['ports']) && !in_array($target['port'], $cookie['ports']))
318
                return false;
319

    
320
                // Verify that the path matches
321
                if (strpos($target['path'], $cookie['path']) !== 0)
322
                return false;
323

    
324
                return true;
325
        }
326

    
327
}
328

    
329
?>