MediaWiki  master
SetCookieCompat.php
Go to the documentation of this file.
1 <?php
2 
3 namespace 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 }
Wikimedia\Http\SetCookieCompat\setrawcookie
static setrawcookie( $name, $value, $options=[])
Temporary emulation for setrawcookie() with a SameSite option.
Definition: SetCookieCompat.php:44
Wikimedia\Http\SetCookieCompat
Definition: SetCookieCompat.php:9
Wikimedia\Http\SetCookieCompat\error
error( $message)
Definition: SetCookieCompat.php:223
Wikimedia\Http\SetCookieCompat\header
header( $header)
Definition: SetCookieCompat.php:231
Wikimedia\Http\SetCookieCompat\setCookieEmulated
setCookieEmulated( $urlEncode, $name, $value, $options=[])
Temporary emulation for setcookie() or setrawcookie() to match PHP 7.4.3.
Definition: SetCookieCompat.php:99
Wikimedia\Http
Utility for parsing a HTTP Accept header value into a weight map.
Definition: HttpAcceptNegotiator.php:3
Wikimedia\Http\SetCookieCompat\time
time()
Definition: SetCookieCompat.php:219
$header
$header
Definition: updateCredits.php:37
$path
$path
Definition: NoLocalSettings.php:25
Wikimedia\Http\SetCookieCompat\setCookieInternal
setCookieInternal( $urlEncode, $name, $value, $options=[])
Definition: SetCookieCompat.php:56
Wikimedia\Http\SetCookieCompat\headers_sent
headers_sent()
Definition: SetCookieCompat.php:227
Wikimedia\Http\SetCookieCompat\setcookie
static setcookie( $name, $value, $options=[])
Temporary emulation for setcookie() with a SameSite option and encoding spaces in values as "%20" rat...
Definition: SetCookieCompat.php:29