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