MediaWiki fundraising/REL1_35
SetCookieCompat.php
Go to the documentation of this file.
1<?php
2
3namespace Wikimedia\Http;
4
29 public static function setcookie( $name, $value, $options = [] ) {
30 return ( new self )->setCookieInternal( true, $name, $value, $options );
31 }
32
44 public static function setrawcookie( $name, $value, $options = [] ) {
45 return ( new self )->setCookieInternal( false, $name, $value, $options );
46 }
47
56 public function setCookieInternal( $urlEncode, $name, $value, $options = [] ) {
57 $supportsAssoc = version_compare( PHP_VERSION, '7.3.0', '>=' );
58 if ( $supportsAssoc ) {
59 if ( $urlEncode && version_compare( PHP_VERSION, '7.4.3', '>=' ) ) {
60 return setcookie( $name, $value, $options );
61 } elseif ( $urlEncode ) {
62 return setrawcookie( $name, rawurlencode( $value ), $options );
63 } else {
64 return setrawcookie( $name, $value, $options );
65 }
66 }
67
68 if ( !isset( $options['samesite'] ) || !strlen( $options['samesite'] ) ) {
69 return setrawcookie(
70 $name,
71 $urlEncode ? rawurlencode( $value ) : $value,
72 $options['expires'],
73 $options['path'],
74 $options['domain'],
75 $options['secure'],
76 $options['httponly']
77 );
78 }
79
80 return self::setCookieEmulated( $urlEncode, $name, $value, $options );
81 }
82
99 public function setCookieEmulated( $urlEncode, $name, $value, $options = [] ) {
100 $func = $urlEncode ? 'setcookie()' : 'setrawcookie()';
101 $expires = 0;
102 $path = null;
103 $domain = null;
104 $secure = false;
105 $httponly = false;
106 $samesite = null;
107 $found = 0;
108 foreach ( $options as $key => $opt ) {
109 if ( $key === 'expires' ) {
110 $expires = (int)$opt;
111 $found++;
112 } elseif ( $key === 'path' ) {
113 $path = (string)$opt;
114 $found++;
115 } elseif ( $key === 'domain' ) {
116 $domain = (string)$opt;
117 $found++;
118 } elseif ( $key === 'secure' ) {
119 $secure = (bool)$opt;
120 $found++;
121 } elseif ( $key === 'httponly' ) {
122 $httponly = (bool)$opt;
123 $found++;
124 } elseif ( $key === 'samesite' ) {
125 $samesite = (string)$opt;
126 $found++;
127 } else {
128 $this->error( "$func: Unrecognized key '$key' found in the options array" );
129 }
130 }
131
132 if ( $found == 0 && count( $options ) > 0 ) {
133 $this->error( "$func: No valid options were found in the given array" );
134 }
135
136 if ( !strlen( $name ) ) {
137 $this->error( 'Cookie names must not be empty' );
138 return false;
139 } elseif ( strpbrk( $name, "=,; \t\r\n\013\014" ) !== false ) {
140 $this->error( "Cookie names cannot contain any of the following " .
141 "'=,; \\t\\r\\n\\013\\014'" );
142 return false;
143 }
144
145 if ( !$urlEncode && $value !== null
146 && strpbrk( $value, ",; \t\r\n\013\014" ) !== false
147 ) {
148 $this->error( "Cookie values cannot contain any of the following ',; \\t\\r\\n\\013\\014'" );
149 return false;
150 }
151
152 if ( $path !== null && strpbrk( $path, ",; \t\r\n\013\014" ) !== false ) {
153 $this->error( "Cookie paths cannot contain any of the following ',; \\t\\r\\n\\013\\014'" );
154 return false;
155 }
156
157 if ( $domain !== null && strpbrk( $domain, ",; \t\r\n\013\014" ) !== false ) {
158 $this->error( "Cookie domains cannot contain any of the following ',; \\t\\r\\n\\013\\014'" );
159 return false;
160 }
161
162 $buf = '';
163 if ( $value === null || strlen( $value ) === 0 ) {
164 $dt = gmdate( "D, d-M-Y H:i:s T", 1 );
165 $buf .= "Set-Cookie: $name=deleted; expires=$dt; Max-Age=0";
166 } else {
167 $buf .= "Set-Cookie: $name=";
168 if ( $urlEncode ) {
169 $buf .= rawurlencode( $value );
170 } else {
171 $buf .= $value;
172 }
173
174 if ( $expires > 0 ) {
175 $dt = gmdate( "D, d-M-Y H:i:s T", $expires );
176 $p = strrpos( $dt, '-' );
177 if ( $p === false || substr( $dt, $p + 5, 1 ) !== ' ' ) {
178 $this->error( "Expiry date cannot have a year greater than 9999" );
179 return false;
180 }
181
182 $buf .= "; expires=$dt";
183
184 $diff = $expires - $this->time();
185 if ( $diff < 0 ) {
186 $diff = 0;
187 }
188 $buf .= "; Max-Age=$diff";
189 }
190 }
191
192 if ( $path !== null && strlen( $path ) ) {
193 $buf .= "; path=$path";
194 }
195 if ( $domain !== null && strlen( $domain ) ) {
196 $buf .= "; domain=$domain";
197 }
198 if ( $secure ) {
199 $buf .= "; secure";
200 }
201 if ( $httponly ) {
202 $buf .= "; HttpOnly";
203 }
204 if ( $samesite !== null && strlen( $samesite ) ) {
205 $buf .= "; SameSite=$samesite";
206 }
207
208 // sapi_header_op() returns a value which setcookie() uses, but
209 // header() discards it. The most likely way for sapi_header_op() to
210 // fail is due to headers already being sent.
211 if ( $this->headers_sent() ) {
212 $this->error( "Cannot modify header information - headers already sent" );
213 return false;
214 }
215 $this->header( $buf );
216 return true;
217 }
218
219 protected function time() {
220 return time();
221 }
222
223 protected function error( $message ) {
224 trigger_error( $message, E_USER_WARNING );
225 }
226
227 protected function headers_sent() {
228 return headers_sent();
229 }
230
231 protected function header( $header ) {
232 header( $header, false );
233 }
234}
setCookieEmulated( $urlEncode, $name, $value, $options=[])
Temporary emulation for setcookie() or setrawcookie() to match PHP 7.4.3.
static setrawcookie( $name, $value, $options=[])
Temporary emulation for setrawcookie() with a SameSite option.
setCookieInternal( $urlEncode, $name, $value, $options=[])
static setcookie( $name, $value, $options=[])
Temporary emulation for setcookie() with a SameSite option and encoding spaces in values as "%20" rat...
Utility for parsing a HTTP Accept header value into a weight map.
$header