MediaWiki REL1_35
WebResponse.php
Go to the documentation of this file.
1<?php
24
31
35 protected static $setCookies = [];
36
38 protected static $disableForPostSend = false;
39
49 public static function disableForPostSend() {
50 self::$disableForPostSend = true;
51 }
52
59 public function header( $string, $replace = true, $http_response_code = null ) {
60 if ( self::$disableForPostSend ) {
61 wfDebugLog( 'header', 'ignored post-send header {header}', 'all', [
62 'header' => $string,
63 'replace' => $replace,
64 'http_response_code' => $http_response_code,
65 'exception' => new RuntimeException( 'Ignored post-send header' ),
66 ] );
67 return;
68 }
69
70 \MediaWiki\HeaderCallback::warnIfHeadersSent();
71 if ( $http_response_code ) {
72 header( $string, $replace, $http_response_code );
73 } else {
74 header( $string, $replace );
75 }
76 }
77
84 public function getHeader( $key ) {
85 foreach ( headers_list() as $header ) {
86 list( $name, $val ) = explode( ':', $header, 2 );
87 if ( !strcasecmp( $name, $key ) ) {
88 return trim( $val );
89 }
90 }
91 return null;
92 }
93
99 public function statusHeader( $code ) {
100 if ( self::$disableForPostSend ) {
101 wfDebugLog( 'header', 'ignored post-send status header {code}', 'all', [
102 'code' => $code,
103 'exception' => new RuntimeException( 'Ignored post-send status header' ),
104 ] );
105 return;
106 }
107
108 HttpStatus::header( $code );
109 }
110
116 public function headersSent() {
117 return headers_sent();
118 }
119
141 public function setCookie( $name, $value, $expire = 0, $options = [] ) {
145
146 $options = array_filter( $options, function ( $a ) {
147 return $a !== null;
148 } ) + [
149 'prefix' => $wgCookiePrefix,
150 'domain' => $wgCookieDomain,
151 'path' => $wgCookiePath,
152 'secure' => $wgCookieSecure,
153 'httpOnly' => $wgCookieHttpOnly,
154 'raw' => false,
155 'sameSite' => '',
156 'sameSiteLegacy' => $wgUseSameSiteLegacyCookies
157 ];
158
159 if ( strcasecmp( $options['sameSite'], 'none' ) === 0
160 && !empty( $options['sameSiteLegacy'] )
161 ) {
162 $legacyOptions = $options;
163 $legacyOptions['sameSiteLegacy'] = false;
164 $legacyOptions['sameSite'] = '';
165 $this->setCookie( "ss0-$name", $value, $expire, $legacyOptions );
166 }
167
168 if ( $expire === null ) {
169 $expire = 0; // Session cookie
170 } elseif ( $expire == 0 && $wgCookieExpiration != 0 ) {
171 $expire = time() + $wgCookieExpiration;
172 }
173
174 if ( self::$disableForPostSend ) {
175 $prefixedName = $options['prefix'] . $name;
176 wfDebugLog( 'cookie', 'ignored post-send cookie {cookie}', 'all', [
177 'cookie' => $prefixedName,
178 'data' => [
179 'name' => $prefixedName,
180 'value' => (string)$value,
181 'expire' => (int)$expire,
182 'path' => (string)$options['path'],
183 'domain' => (string)$options['domain'],
184 'secure' => (bool)$options['secure'],
185 'httpOnly' => (bool)$options['httpOnly'],
186 'sameSite' => (string)$options['sameSite']
187 ],
188 'exception' => new RuntimeException( 'Ignored post-send cookie' ),
189 ] );
190 return;
191 }
192
193 if ( !Hooks::runner()->onWebResponseSetCookie( $name, $value, $expire, $options ) ) {
194 return;
195 }
196
197 // Note: Don't try to move this earlier to reuse it for self::$disableForPostSend,
198 // we need to use the altered values from the hook here. (T198525)
199 $prefixedName = $options['prefix'] . $name;
200 $value = (string)$value;
201 $func = $options['raw'] ? 'setrawcookie' : 'setcookie';
202 $setOptions = [
203 'expires' => (int)$expire,
204 'path' => (string)$options['path'],
205 'domain' => (string)$options['domain'],
206 'secure' => (bool)$options['secure'],
207 'httponly' => (bool)$options['httpOnly'],
208 'samesite' => (string)$options['sameSite'],
209 ];
210
211 // Per RFC 6265, key is name + domain + path
212 $key = "{$prefixedName}\n{$setOptions['domain']}\n{$setOptions['path']}";
213
214 // If this cookie name was in the request, fake an entry in
215 // self::$setCookies for it so the deleting check works right.
216 if ( isset( $_COOKIE[$prefixedName] ) && !array_key_exists( $key, self::$setCookies ) ) {
217 self::$setCookies[$key] = [];
218 }
219
220 // PHP deletes if value is the empty string; also, a past expiry is deleting
221 $deleting = ( $value === '' || $setOptions['expires'] > 0 && $setOptions['expires'] <= time() );
222
223 $logDesc = "$func: \"$prefixedName\", \"$value\", \"" .
224 implode( '", "', $setOptions ) . '"';
225 $optionsForDeduplication = [ $func, $prefixedName, $value, $setOptions ];
226
227 if ( $deleting && !isset( self::$setCookies[$key] ) ) { // isset( null ) is false
228 wfDebugLog( 'cookie', "already deleted $logDesc" );
229 return;
230 } elseif ( !$deleting && isset( self::$setCookies[$key] ) &&
231 self::$setCookies[$key] === $optionsForDeduplication
232 ) {
233 wfDebugLog( 'cookie', "already set $logDesc" );
234 return;
235 }
236
237 wfDebugLog( 'cookie', $logDesc );
238 if ( $func === 'setrawcookie' ) {
239 SetCookieCompat::setrawcookie( $prefixedName, $value, $setOptions );
240 } else {
241 SetCookieCompat::setcookie( $prefixedName, $value, $setOptions );
242 }
243 self::$setCookies[$key] = $deleting ? null : $optionsForDeduplication;
244 }
245
255 public function clearCookie( $name, $options = [] ) {
256 $this->setCookie( $name, '', time() - 31536000 /* 1 year */, $options );
257 }
258
265 public function hasCookies() {
266 return (bool)self::$setCookies;
267 }
268}
$wgCookieExpiration
Default cookie lifetime, in seconds.
bool $wgUseSameSiteLegacyCookies
If true, when a cross-site cookie with SameSite=None is sent, a legacy cookie with an "ss0" prefix wi...
$wgCookieHttpOnly
Set authentication cookies to HttpOnly to prevent access by JavaScript, in browsers that support this...
$wgCookiePath
Set this variable if you want to restrict cookies to a certain path within the domain specified by $w...
$wgCookieDomain
Set to set an explicit domain on the login cookies eg, "justthis.domain.org" or "....
$wgCookieSecure
Whether the "secure" flag should be set on the cookie.
$wgCookiePrefix
Cookies generated by MediaWiki have names starting with this prefix.
wfDebugLog( $logGroup, $text, $dest='all', array $context=[])
Send a line to a supplementary debug log file, if configured, or main debug log if not.
Allow programs to request this object from WebRequest::response() and handle all outputting (or lack ...
setCookie( $name, $value, $expire=0, $options=[])
Set the browser cookie.
hasCookies()
Checks whether this request is performing cookie operations.
static bool $disableForPostSend
Used to disable setters before running jobs post-request (T191537)
getHeader( $key)
Get a response header.
statusHeader( $code)
Output an HTTP status code header.
static disableForPostSend()
Disable setters for post-send processing.
header( $string, $replace=true, $http_response_code=null)
Output an HTTP header, wrapper for PHP's header()
clearCookie( $name, $options=[])
Unset a browser cookie.
headersSent()
Test if headers have been sent.
static array $setCookies
Used to record set cookies, because PHP's setcookie() will happily send an identical Set-Cookie to th...
$header