Completed
Branch master (af7ffa)
by
unknown
24:08
created

FauxResponse::setCookie()   B

Complexity

Conditions 4
Paths 3

Size

Total Lines 31
Code Lines 23

Duplication

Lines 5
Ratio 16.13 %

Importance

Changes 0
Metric Value
cc 4
eloc 23
nc 3
nop 4
dl 5
loc 31
rs 8.5806
c 0
b 0
f 0
1
<?php
2
/**
3
 * Classes used to send headers and cookies back to the user
4
 *
5
 * This program is free software; you can redistribute it and/or modify
6
 * it under the terms of the GNU General Public License as published by
7
 * the Free Software Foundation; either version 2 of the License, or
8
 * (at your option) any later version.
9
 *
10
 * This program is distributed in the hope that it will be useful,
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
 * GNU General Public License for more details.
14
 *
15
 * You should have received a copy of the GNU General Public License along
16
 * with this program; if not, write to the Free Software Foundation, Inc.,
17
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18
 * http://www.gnu.org/copyleft/gpl.html
19
 *
20
 * @file
21
 */
22
23
/**
24
 * Allow programs to request this object from WebRequest::response()
25
 * and handle all outputting (or lack of outputting) via it.
26
 * @ingroup HTTP
27
 */
28
class WebResponse {
29
30
	/** @var array Used to record set cookies, because PHP's setcookie() will
31
	 * happily send an identical Set-Cookie to the client.
32
	 */
33
	protected static $setCookies = [];
34
35
	/**
36
	 * Output an HTTP header, wrapper for PHP's header()
37
	 * @param string $string Header to output
38
	 * @param bool $replace Replace current similar header
39
	 * @param null|int $http_response_code Forces the HTTP response code to the specified value.
40
	 */
41
	public function header( $string, $replace = true, $http_response_code = null ) {
42
		header( $string, $replace, $http_response_code );
43
	}
44
45
	/**
46
	 * Get a response header
47
	 * @param string $key The name of the header to get (case insensitive).
48
	 * @return string|null The header value (if set); null otherwise.
49
	 * @since 1.25
50
	 */
51
	public function getHeader( $key ) {
52
		foreach ( headers_list() as $header ) {
53
			list( $name, $val ) = explode( ':', $header, 2 );
54
			if ( !strcasecmp( $name, $key ) ) {
55
				return trim( $val );
56
			}
57
		}
58
		return null;
59
	}
60
61
	/**
62
	 * Output an HTTP status code header
63
	 * @since 1.26
64
	 * @param int $code Status code
65
	 */
66
	public function statusHeader( $code ) {
67
		HttpStatus::header( $code );
68
	}
69
70
	/**
71
	 * Test if headers have been sent
72
	 * @since 1.27
73
	 * @return bool
74
	 */
75
	public function headersSent() {
76
		return headers_sent();
77
	}
78
79
	/**
80
	 * Set the browser cookie
81
	 * @param string $name The name of the cookie.
82
	 * @param string $value The value to be stored in the cookie.
83
	 * @param int|null $expire Unix timestamp (in seconds) when the cookie should expire.
84
	 *        0 (the default) causes it to expire $wgCookieExpiration seconds from now.
85
	 *        null causes it to be a session cookie.
86
	 * @param array $options Assoc of additional cookie options:
87
	 *     prefix: string, name prefix ($wgCookiePrefix)
88
	 *     domain: string, cookie domain ($wgCookieDomain)
89
	 *     path: string, cookie path ($wgCookiePath)
90
	 *     secure: bool, secure attribute ($wgCookieSecure)
91
	 *     httpOnly: bool, httpOnly attribute ($wgCookieHttpOnly)
92
	 * @since 1.22 Replaced $prefix, $domain, and $forceSecure with $options
93
	 */
94
	public function setCookie( $name, $value, $expire = 0, $options = [] ) {
0 ignored issues
show
Coding Style introduced by
setCookie uses the super-global variable $_COOKIE which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
95
		global $wgCookiePath, $wgCookiePrefix, $wgCookieDomain;
96
		global $wgCookieSecure, $wgCookieExpiration, $wgCookieHttpOnly;
97
98
		$options = array_filter( $options, function ( $a ) {
99
			return $a !== null;
100
		} ) + [
101
			'prefix' => $wgCookiePrefix,
102
			'domain' => $wgCookieDomain,
103
			'path' => $wgCookiePath,
104
			'secure' => $wgCookieSecure,
105
			'httpOnly' => $wgCookieHttpOnly,
106
			'raw' => false,
107
		];
108
109 View Code Duplication
		if ( $expire === null ) {
110
			$expire = 0; // Session cookie
111
		} elseif ( $expire == 0 && $wgCookieExpiration != 0 ) {
112
			$expire = time() + $wgCookieExpiration;
113
		}
114
115
		$func = $options['raw'] ? 'setrawcookie' : 'setcookie';
116
117
		if ( Hooks::run( 'WebResponseSetCookie', [ &$name, &$value, &$expire, &$options ] ) ) {
118
			$cookie = $options['prefix'] . $name;
119
			$data = [
120
				'name' => (string)$cookie,
121
				'value' => (string)$value,
122
				'expire' => (int)$expire,
123
				'path' => (string)$options['path'],
124
				'domain' => (string)$options['domain'],
125
				'secure' => (bool)$options['secure'],
126
				'httpOnly' => (bool)$options['httpOnly'],
127
			];
128
129
			// Per RFC 6265, key is name + domain + path
130
			$key = "{$data['name']}\n{$data['domain']}\n{$data['path']}";
131
132
			// If this cookie name was in the request, fake an entry in
133
			// self::$setCookies for it so the deleting check works right.
134
			if ( isset( $_COOKIE[$cookie] ) && !array_key_exists( $key, self::$setCookies ) ) {
135
				self::$setCookies[$key] = [];
136
			}
137
138
			// PHP deletes if value is the empty string; also, a past expiry is deleting
139
			$deleting = ( $data['value'] === '' || $data['expire'] > 0 && $data['expire'] <= time() );
140
141
			if ( $deleting && !isset( self::$setCookies[$key] ) ) { // isset( null ) is false
142
				wfDebugLog( 'cookie', 'already deleted ' . $func . ': "' . implode( '", "', $data ) . '"' );
143
			} elseif ( !$deleting && isset( self::$setCookies[$key] ) &&
144
				self::$setCookies[$key] === [ $func, $data ]
145
			) {
146
				wfDebugLog( 'cookie', 'already set ' . $func . ': "' . implode( '", "', $data ) . '"' );
147
			} else {
148
				wfDebugLog( 'cookie', $func . ': "' . implode( '", "', $data ) . '"' );
149
				if ( call_user_func_array( $func, array_values( $data ) ) ) {
150
					self::$setCookies[$key] = $deleting ? null : [ $func, $data ];
151
				}
152
			}
153
		}
154
	}
155
156
	/**
157
	 * Unset a browser cookie.
158
	 * This sets the cookie with an empty value and an expiry set to a time in the past,
159
	 * which will cause the browser to remove any cookie with the given name, domain and
160
	 * path from its cookie store. Options other than these (and prefix) have no effect.
161
	 * @param string $name Cookie name
162
	 * @param array $options Cookie options, see {@link setCookie()}
163
	 * @since 1.27
164
	 */
165
	public function clearCookie( $name, $options = [] ) {
166
		$this->setCookie( $name, '', time() - 31536000 /* 1 year */, $options );
167
	}
168
169
	/**
170
	 * Checks whether this request is performing cookie operations
171
	 *
172
	 * @return bool
173
	 * @since 1.27
174
	 */
175
	public function hasCookies() {
176
		return (bool)self::$setCookies;
177
	}
178
}
179
180
/**
181
 * @ingroup HTTP
182
 */
183
class FauxResponse extends WebResponse {
184
	private $headers;
185
	private $cookies = [];
186
	private $code;
187
188
	/**
189
	 * Stores a HTTP header
190
	 * @param string $string Header to output
191
	 * @param bool $replace Replace current similar header
192
	 * @param null|int $http_response_code Forces the HTTP response code to the specified value.
193
	 */
194
	public function header( $string, $replace = true, $http_response_code = null ) {
195
		if ( substr( $string, 0, 5 ) == 'HTTP/' ) {
196
			$parts = explode( ' ', $string, 3 );
197
			$this->code = intval( $parts[1] );
198
		} else {
199
			list( $key, $val ) = array_map( 'trim', explode( ":", $string, 2 ) );
200
201
			$key = strtoupper( $key );
202
203
			if ( $replace || !isset( $this->headers[$key] ) ) {
204
				$this->headers[$key] = $val;
205
			}
206
		}
207
208
		if ( $http_response_code !== null ) {
209
			$this->code = intval( $http_response_code );
210
		}
211
	}
212
213
	/**
214
	 * @since 1.26
215
	 * @param int $code Status code
216
	 */
217
	public function statusHeader( $code ) {
218
		$this->code = intval( $code );
219
	}
220
221
	public function headersSent() {
222
		return false;
223
	}
224
225
	/**
226
	 * @param string $key The name of the header to get (case insensitive).
227
	 * @return string|null The header value (if set); null otherwise.
228
	 */
229
	public function getHeader( $key ) {
230
		$key = strtoupper( $key );
231
232
		if ( isset( $this->headers[$key] ) ) {
233
			return $this->headers[$key];
234
		}
235
		return null;
236
	}
237
238
	/**
239
	 * Get the HTTP response code, null if not set
240
	 *
241
	 * @return int|null
242
	 */
243
	public function getStatusCode() {
244
		return $this->code;
245
	}
246
247
	/**
248
	 * @param string $name The name of the cookie.
249
	 * @param string $value The value to be stored in the cookie.
250
	 * @param int|null $expire Ignored in this faux subclass.
251
	 * @param array $options Ignored in this faux subclass.
252
	 */
253
	public function setCookie( $name, $value, $expire = 0, $options = [] ) {
254
		global $wgCookiePath, $wgCookiePrefix, $wgCookieDomain;
255
		global $wgCookieSecure, $wgCookieExpiration, $wgCookieHttpOnly;
256
257
		$options = array_filter( $options, function ( $a ) {
258
			return $a !== null;
259
		} ) + [
260
			'prefix' => $wgCookiePrefix,
261
			'domain' => $wgCookieDomain,
262
			'path' => $wgCookiePath,
263
			'secure' => $wgCookieSecure,
264
			'httpOnly' => $wgCookieHttpOnly,
265
			'raw' => false,
266
		];
267
268 View Code Duplication
		if ( $expire === null ) {
269
			$expire = 0; // Session cookie
270
		} elseif ( $expire == 0 && $wgCookieExpiration != 0 ) {
271
			$expire = time() + $wgCookieExpiration;
272
		}
273
274
		$this->cookies[$options['prefix'] . $name] = [
275
			'value' => (string)$value,
276
			'expire' => (int)$expire,
277
			'path' => (string)$options['path'],
278
			'domain' => (string)$options['domain'],
279
			'secure' => (bool)$options['secure'],
280
			'httpOnly' => (bool)$options['httpOnly'],
281
			'raw' => (bool)$options['raw'],
282
		];
283
	}
284
285
	/**
286
	 * @param string $name
287
	 * @return string|null
288
	 */
289
	public function getCookie( $name ) {
290
		if ( isset( $this->cookies[$name] ) ) {
291
			return $this->cookies[$name]['value'];
292
		}
293
		return null;
294
	}
295
296
	/**
297
	 * @param string $name
298
	 * @return array|null
299
	 */
300
	public function getCookieData( $name ) {
301
		if ( isset( $this->cookies[$name] ) ) {
302
			return $this->cookies[$name];
303
		}
304
		return null;
305
	}
306
307
	/**
308
	 * @return array
309
	 */
310
	public function getCookies() {
311
		return $this->cookies;
312
	}
313
}
314