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