1
|
|
|
<?php |
2
|
|
|
namespace PHPDaemon\Servers\WebSocket; |
3
|
|
|
|
4
|
|
|
use PHPDaemon\Core\Daemon; |
5
|
|
|
use PHPDaemon\HTTPRequest\Generic; |
6
|
|
|
use PHPDaemon\WebSocket\Route; |
7
|
|
|
use PHPDaemon\Request\RequestHeadersAlreadySent; |
8
|
|
|
|
9
|
|
|
class Connection extends \PHPDaemon\Network\Connection { |
10
|
|
|
use \PHPDaemon\Traits\DeferredEventHandlers; |
11
|
|
|
use \PHPDaemon\Traits\Sessions; |
12
|
|
|
|
13
|
|
|
/** |
14
|
|
|
* @var integer Timeout |
15
|
|
|
*/ |
16
|
|
|
protected $timeout = 120; |
17
|
|
|
|
18
|
|
|
protected $handshaked = false; |
19
|
|
|
protected $route; |
20
|
|
|
protected $writeReady = true; |
21
|
|
|
protected $extensions = []; |
22
|
|
|
protected $extensionsCleanRegex = '/(?:^|\W)x-webkit-/iS'; |
23
|
|
|
|
24
|
|
|
protected $headers = []; |
25
|
|
|
protected $headers_sent = false; |
26
|
|
|
|
27
|
|
|
/** |
28
|
|
|
* @var array _SERVER |
29
|
|
|
*/ |
30
|
|
|
public $server = []; |
31
|
|
|
|
32
|
|
|
/** |
33
|
|
|
* @var array _COOKIE |
34
|
|
|
*/ |
35
|
|
|
public $cookie = []; |
36
|
|
|
|
37
|
|
|
/** |
38
|
|
|
* @var array _GET |
39
|
|
|
*/ |
40
|
|
|
public $get = []; |
41
|
|
|
|
42
|
|
|
|
43
|
|
|
protected $policyReqNotFound = false; |
44
|
|
|
protected $currentHeader; |
45
|
|
|
protected $EOL = "\r\n"; |
46
|
|
|
|
47
|
|
|
/** |
48
|
|
|
* @var boolean Is this connection running right now? |
49
|
|
|
*/ |
50
|
|
|
protected $running = false; |
51
|
|
|
|
52
|
|
|
/** |
53
|
|
|
* State: first line |
54
|
|
|
*/ |
55
|
|
|
const STATE_FIRSTLINE = 1; |
56
|
|
|
|
57
|
|
|
/** |
58
|
|
|
* State: headers |
59
|
|
|
*/ |
60
|
|
|
const STATE_HEADERS = 2; |
61
|
|
|
|
62
|
|
|
/** |
63
|
|
|
* State: content |
64
|
|
|
*/ |
65
|
|
|
const STATE_CONTENT = 3; |
66
|
|
|
|
67
|
|
|
/** |
68
|
|
|
* State: prehandshake |
69
|
|
|
*/ |
70
|
|
|
const STATE_PREHANDSHAKE = 5; |
71
|
|
|
|
72
|
|
|
/** |
73
|
|
|
* State: handshaked |
74
|
|
|
*/ |
75
|
|
|
const STATE_HANDSHAKED = 6; |
76
|
|
|
|
77
|
|
|
const STRING = NULL; |
78
|
|
|
|
79
|
|
|
const BINARY = NULL; |
80
|
|
|
|
81
|
|
|
/** |
82
|
|
|
* @var integer Content length from header() method |
83
|
|
|
*/ |
84
|
|
|
protected $contentLength; |
85
|
|
|
|
86
|
|
|
/** |
87
|
|
|
* @var integer Number of outgoing cookie-headers |
88
|
|
|
*/ |
89
|
|
|
protected $cookieNum = 0; |
90
|
|
|
|
91
|
|
|
/** |
92
|
|
|
* @var array Replacement pairs for processing some header values in parse_str() |
93
|
|
|
*/ |
94
|
|
|
public static $hvaltr = ['; ' => '&', ';' => '&', ' ' => '%20']; |
95
|
|
|
|
96
|
|
|
/** |
97
|
|
|
* Called when the stream is handshaked (at low-level), and peer is ready to recv. data |
98
|
|
|
* @return void |
99
|
|
|
*/ |
100
|
|
|
public function onReady() { |
101
|
|
|
$this->setWatermark(null, $this->pool->maxAllowedPacket + 100); |
102
|
|
|
} |
103
|
|
|
|
104
|
|
|
/** |
105
|
|
|
* Get real frame type identificator |
106
|
|
|
* @param $type |
107
|
|
|
* @return integer |
108
|
|
|
*/ |
109
|
|
|
public function getFrameType($type) { |
110
|
|
|
if (is_int($type)) { |
111
|
|
|
return $type; |
112
|
|
|
} |
113
|
|
|
if ($type === null) { |
114
|
|
|
$type = 'STRING'; |
115
|
|
|
} |
116
|
|
|
$frametype = @constant(get_class($this) . '::' . $type); |
117
|
|
|
if ($frametype === null) { |
118
|
|
|
Daemon::log(__METHOD__ . ' : Undefined frametype "' . $type . '"'); |
119
|
|
|
} |
120
|
|
|
return $frametype; |
121
|
|
|
} |
122
|
|
|
|
123
|
|
|
|
124
|
|
|
/** |
125
|
|
|
* Called when connection is inherited from HTTP request |
126
|
|
|
* @param object $req |
127
|
|
|
* @return void |
128
|
|
|
*/ |
129
|
|
View Code Duplication |
public function onInheritanceFromRequest($req) { |
|
|
|
|
130
|
|
|
$this->state = self::STATE_HEADERS; |
131
|
|
|
$this->addr = $req->attrs->server['REMOTE_ADDR']; |
132
|
|
|
$this->server = $req->attrs->server; |
133
|
|
|
$this->get = $req->attrs->get; |
134
|
|
|
$this->prependInput("\r\n"); |
135
|
|
|
$this->onRead(); |
136
|
|
|
} |
137
|
|
|
|
138
|
|
|
/** |
139
|
|
|
* Sends a frame. |
140
|
|
|
* @param string $data Frame's data. |
141
|
|
|
* @param string $type Frame's type. ("STRING" OR "BINARY") |
|
|
|
|
142
|
|
|
* @param callable $cb Optional. Callback called when the frame is received by client. |
|
|
|
|
143
|
|
|
* @callback $cb ( ) |
144
|
|
|
* @return boolean Success. |
145
|
|
|
*/ |
146
|
|
|
public function sendFrame($data, $type = null, $cb = null) { |
147
|
|
|
return false; |
148
|
|
|
} |
149
|
|
|
|
150
|
|
|
/** |
151
|
|
|
* Event of Connection. |
152
|
|
|
* @return void |
153
|
|
|
*/ |
154
|
|
|
public function onFinish() { |
155
|
|
|
|
156
|
|
|
$this->sendFrame('', 'CONNCLOSE'); |
157
|
|
|
|
158
|
|
|
if ($this->route) { |
159
|
|
|
$this->route->onFinish(); |
160
|
|
|
} |
161
|
|
|
$this->route = null; |
162
|
|
|
} |
163
|
|
|
|
164
|
|
|
/** |
165
|
|
|
* Uncaught exception handler |
166
|
|
|
* @param Exception $e |
167
|
|
|
* @return boolean Handled? |
168
|
|
|
*/ |
169
|
|
|
public function handleException($e) { |
170
|
|
|
if (!isset($this->route)) { |
171
|
|
|
return false; |
172
|
|
|
} |
173
|
|
|
return $this->route->handleException($e); |
174
|
|
|
} |
175
|
|
|
|
176
|
|
|
/** |
177
|
|
|
* Called when new frame received. |
178
|
|
|
* @param string $data Frame's data. |
179
|
|
|
* @param string $type Frame's type ("STRING" OR "BINARY"). |
180
|
|
|
* @return boolean Success. |
181
|
|
|
*/ |
182
|
|
|
public function onFrame($data, $type) { |
183
|
|
|
if (!isset($this->route)) { |
184
|
|
|
return false; |
185
|
|
|
} |
186
|
|
|
try { |
187
|
|
|
$this->route->onWakeup(); |
188
|
|
|
$this->route->onFrame($data, $type); |
189
|
|
|
} catch (\Exception $e) { |
190
|
|
|
Daemon::uncaughtExceptionHandler($e); |
191
|
|
|
} |
192
|
|
|
$this->route->onSleep(); |
193
|
|
|
return true; |
194
|
|
|
} |
195
|
|
|
|
196
|
|
|
/** |
197
|
|
|
* Called when the worker is going to shutdown. |
198
|
|
|
* @return boolean Ready to shutdown ? |
199
|
|
|
*/ |
200
|
|
|
public function gracefulShutdown() { |
201
|
|
|
if ((!$this->route) || $this->route->gracefulShutdown()) { |
202
|
|
|
$this->finish(); |
203
|
|
|
return true; |
204
|
|
|
} |
205
|
|
|
return FALSE; |
206
|
|
|
} |
207
|
|
|
|
208
|
|
|
|
209
|
|
|
/** |
210
|
|
|
* Called when we're going to handshake. |
211
|
|
|
* @return boolean Handshake status |
|
|
|
|
212
|
|
|
*/ |
213
|
|
|
public function handshake() { |
214
|
|
|
$this->route = $this->pool->getRoute($this->server['DOCUMENT_URI'], $this); |
215
|
|
View Code Duplication |
if (!$this->route) { |
|
|
|
|
216
|
|
|
Daemon::$process->log(get_class($this) . '::' . __METHOD__ . ' : Cannot handshake session for client "' . $this->addr . '"'); |
|
|
|
|
217
|
|
|
$this->finish(); |
218
|
|
|
return false; |
219
|
|
|
} |
220
|
|
|
|
221
|
|
|
if (method_exists($this->route, 'onBeforeHandshake')) { |
222
|
|
|
$this->route->onWakeup(); |
223
|
|
|
$ret = $this->route->onBeforeHandshake(function() { |
224
|
|
|
$extraHeaders = ''; |
225
|
|
|
foreach ($this->headers as $k => $line) { |
226
|
|
|
if ($k !== 'STATUS') { |
227
|
|
|
$extraHeaders .= $line . "\r\n"; |
228
|
|
|
} |
229
|
|
|
} |
230
|
|
|
|
231
|
|
|
$this->handshakeAfter($extraHeaders); |
232
|
|
|
}); |
233
|
|
|
$this->route->onSleep(); |
234
|
|
|
if ($ret !== false) { |
235
|
|
|
return; |
236
|
|
|
} |
237
|
|
|
} |
238
|
|
|
|
239
|
|
|
$this->handshakeAfter(); |
240
|
|
|
} |
241
|
|
|
|
242
|
|
|
protected function handshakeAfter($extraHeaders = '') { |
243
|
|
View Code Duplication |
if (!$this->sendHandshakeReply($extraHeaders)) { |
|
|
|
|
244
|
|
|
Daemon::$process->log(get_class($this) . '::' . __METHOD__ . ' : Handshake protocol failure for client "' . $this->addr . '"'); |
|
|
|
|
245
|
|
|
$this->finish(); |
246
|
|
|
return false; |
247
|
|
|
} |
248
|
|
|
|
249
|
|
|
$this->handshaked = true; |
250
|
|
|
$this->headers_sent = true; |
251
|
|
|
$this->state = static::STATE_HANDSHAKED; |
252
|
|
|
if (is_callable([$this->route, 'onHandshake'])) { |
253
|
|
|
$this->route->onWakeup(); |
|
|
|
|
254
|
|
|
$this->route->onHandshake(); |
|
|
|
|
255
|
|
|
$this->route->onSleep(); |
|
|
|
|
256
|
|
|
} |
257
|
|
|
return true; |
258
|
|
|
} |
259
|
|
|
|
260
|
|
|
/** |
261
|
|
|
* Send Bad request |
262
|
|
|
* @return void |
263
|
|
|
*/ |
264
|
|
|
public function badRequest() { |
265
|
|
|
$this->state = self::STATE_STANDBY; |
266
|
|
|
$this->write("400 Bad Request\r\n\r\n<html><head><title>400 Bad Request</title></head><body bgcolor=\"white\"><center><h1>400 Bad Request</h1></center></body></html>"); |
|
|
|
|
267
|
|
|
$this->finish(); |
268
|
|
|
} |
269
|
|
|
|
270
|
|
|
/** |
271
|
|
|
* Read first line of HTTP request |
272
|
|
|
* @return boolean|null Success |
273
|
|
|
*/ |
274
|
|
|
protected function httpReadFirstline() { |
275
|
|
|
if (($l = $this->readline()) === null) { |
276
|
|
|
return null; |
277
|
|
|
} |
278
|
|
|
$e = explode(' ', $l); |
279
|
|
|
$u = isset($e[1]) ? parse_url($e[1]) : false; |
280
|
|
|
if ($u === false) { |
281
|
|
|
$this->badRequest(); |
282
|
|
|
return false; |
283
|
|
|
} |
284
|
|
|
if (!isset($u['path'])) { |
285
|
|
|
$u['path'] = null; |
286
|
|
|
} |
287
|
|
|
if (isset($u['host'])) { |
288
|
|
|
$this->server['HTTP_HOST'] = $u['host']; |
289
|
|
|
} |
290
|
|
|
$srv = & $this->server; |
291
|
|
|
$srv['REQUEST_METHOD'] = $e[0]; |
292
|
|
|
$srv['REQUEST_TIME'] = time(); |
293
|
|
|
$srv['REQUEST_TIME_FLOAT'] = microtime(true); |
294
|
|
|
$srv['REQUEST_URI'] = $u['path'] . (isset($u['query']) ? '?' . $u['query'] : ''); |
295
|
|
|
$srv['DOCUMENT_URI'] = $u['path']; |
296
|
|
|
$srv['PHP_SELF'] = $u['path']; |
297
|
|
|
$srv['QUERY_STRING'] = isset($u['query']) ? $u['query'] : null; |
298
|
|
|
$srv['SCRIPT_NAME'] = $srv['DOCUMENT_URI'] = isset($u['path']) ? $u['path'] : '/'; |
299
|
|
|
$srv['SERVER_PROTOCOL'] = isset($e[2]) ? $e[2] : 'HTTP/1.1'; |
300
|
|
|
$srv['REMOTE_ADDR'] = $this->addr; |
301
|
|
|
$srv['REMOTE_PORT'] = $this->port; |
302
|
|
|
return true; |
303
|
|
|
} |
304
|
|
|
|
305
|
|
|
/** |
306
|
|
|
* Read headers line-by-line |
307
|
|
|
* @return boolean|null Success |
308
|
|
|
*/ |
309
|
|
|
protected function httpReadHeaders() { |
310
|
|
|
while (($l = $this->readLine()) !== null) { |
311
|
|
|
if ($l === '') { |
312
|
|
|
return true; |
313
|
|
|
} |
314
|
|
|
$e = explode(': ', $l); |
315
|
|
|
if (isset($e[1])) { |
316
|
|
|
$this->currentHeader = 'HTTP_' . strtoupper(strtr($e[0], Generic::$htr)); |
317
|
|
|
$this->server[$this->currentHeader] = $e[1]; |
318
|
|
|
} |
319
|
|
View Code Duplication |
elseif (($e[0][0] === "\t" || $e[0][0] === "\x20") && $this->currentHeader) { |
|
|
|
|
320
|
|
|
// multiline header continued |
321
|
|
|
$this->server[$this->currentHeader] .= $e[0]; |
322
|
|
|
} |
323
|
|
|
else { |
324
|
|
|
// whatever client speaks is not HTTP anymore |
325
|
|
|
$this->badRequest(); |
326
|
|
|
return false; |
327
|
|
|
} |
328
|
|
|
} |
329
|
|
|
return null; |
330
|
|
|
} |
331
|
|
|
|
332
|
|
|
/** |
333
|
|
|
* Called when new data received. |
334
|
|
|
* @return void |
335
|
|
|
*/ |
336
|
|
|
protected function onRead() { |
337
|
|
View Code Duplication |
if (!$this->policyReqNotFound) { |
|
|
|
|
338
|
|
|
$d = $this->drainIfMatch("<policy-file-request/>\x00"); |
339
|
|
|
if ($d === null) { // partially match |
340
|
|
|
return; |
341
|
|
|
} |
342
|
|
|
if ($d) { |
343
|
|
|
if (($FP = \PHPDaemon\Servers\FlashPolicy\Pool::getInstance($this->pool->config->fpsname->value, false)) && $FP->policyData) { |
|
|
|
|
344
|
|
|
$this->write($FP->policyData . "\x00"); |
345
|
|
|
} |
346
|
|
|
$this->finish(); |
347
|
|
|
return; |
348
|
|
|
} |
349
|
|
|
else { |
350
|
|
|
$this->policyReqNotFound = true; |
351
|
|
|
} |
352
|
|
|
} |
353
|
|
|
start: |
354
|
|
|
if ($this->finished) { |
355
|
|
|
return; |
356
|
|
|
} |
357
|
|
|
if ($this->state === self::STATE_STANDBY) { |
358
|
|
|
$this->state = self::STATE_FIRSTLINE; |
359
|
|
|
} |
360
|
|
|
if ($this->state === self::STATE_FIRSTLINE) { |
361
|
|
|
if (!$this->httpReadFirstline()) { |
|
|
|
|
362
|
|
|
return; |
363
|
|
|
} |
364
|
|
|
$this->state = self::STATE_HEADERS; |
365
|
|
|
} |
366
|
|
|
|
367
|
|
View Code Duplication |
if ($this->state === self::STATE_HEADERS) { |
|
|
|
|
368
|
|
|
if (!$this->httpReadHeaders()) { |
|
|
|
|
369
|
|
|
return; |
370
|
|
|
} |
371
|
|
|
if (!$this->httpProcessHeaders()) { |
372
|
|
|
$this->finish(); |
373
|
|
|
return; |
374
|
|
|
} |
375
|
|
|
$this->state = self::STATE_CONTENT; |
376
|
|
|
} |
377
|
|
|
if ($this->state === self::STATE_CONTENT) { |
378
|
|
|
$this->state = self::STATE_PREHANDSHAKE; |
379
|
|
|
} |
380
|
|
|
} |
381
|
|
|
|
382
|
|
|
/** |
383
|
|
|
* Process headers |
384
|
|
|
* @return bool |
385
|
|
|
*/ |
386
|
|
|
protected function httpProcessHeaders() { |
387
|
|
|
$this->state = self::STATE_PREHANDSHAKE; |
388
|
|
|
if (isset($this->server['HTTP_SEC_WEBSOCKET_EXTENSIONS'])) { |
389
|
|
|
$str = strtolower($this->server['HTTP_SEC_WEBSOCKET_EXTENSIONS']); |
390
|
|
|
$str = preg_replace($this->extensionsCleanRegex, '', $str); |
391
|
|
|
$this->extensions = explode(', ', $str); |
392
|
|
|
} |
393
|
|
|
if (!isset($this->server['HTTP_CONNECTION']) |
394
|
|
|
|| (!preg_match('~(?:^|\W)Upgrade(?:\W|$)~i', $this->server['HTTP_CONNECTION'])) // "Upgrade" is not always alone (ie. "Connection: Keep-alive, Upgrade") |
|
|
|
|
395
|
|
|
|| !isset($this->server['HTTP_UPGRADE']) |
396
|
|
|
|| (strtolower($this->server['HTTP_UPGRADE']) !== 'websocket') // Lowercase compare important |
397
|
|
|
) { |
398
|
|
|
$this->finish(); |
399
|
|
|
return false; |
400
|
|
|
} |
401
|
|
View Code Duplication |
if (isset($this->server['HTTP_COOKIE'])) { |
|
|
|
|
402
|
|
|
Generic::parse_str(strtr($this->server['HTTP_COOKIE'], Generic::$hvaltr), $this->cookie); |
403
|
|
|
} |
404
|
|
|
if (isset($this->server['QUERY_STRING'])) { |
405
|
|
|
Generic::parse_str($this->server['QUERY_STRING'], $this->get); |
406
|
|
|
} |
407
|
|
|
// ---------------------------------------------------------- |
408
|
|
|
// Protocol discovery, based on HTTP headers... |
409
|
|
|
// ---------------------------------------------------------- |
410
|
|
|
if (isset($this->server['HTTP_SEC_WEBSOCKET_VERSION'])) { // HYBI |
411
|
|
|
if ($this->server['HTTP_SEC_WEBSOCKET_VERSION'] === '8') { // Version 8 (FF7, Chrome14) |
|
|
|
|
412
|
|
|
$this->switchToProtocol('V13'); |
413
|
|
|
} |
414
|
|
|
elseif ($this->server['HTTP_SEC_WEBSOCKET_VERSION'] === '13') { // newest protocol |
415
|
|
|
$this->switchToProtocol('V13'); |
416
|
|
|
} |
417
|
|
|
else { |
418
|
|
|
Daemon::$process->log(get_class($this) . '::' . __METHOD__ . " : Websocket protocol version " . $this->server['HTTP_SEC_WEBSOCKET_VERSION'] . ' is not yet supported for client "' . $this->addr . '"'); |
|
|
|
|
419
|
|
|
$this->finish(); |
420
|
|
|
return false; |
421
|
|
|
} |
422
|
|
|
} |
423
|
|
View Code Duplication |
elseif (!isset($this->server['HTTP_SEC_WEBSOCKET_KEY1']) || !isset($this->server['HTTP_SEC_WEBSOCKET_KEY2'])) { |
|
|
|
|
424
|
|
|
$this->switchToProtocol('VE'); |
425
|
|
|
} |
426
|
|
|
else { // Defaulting to HIXIE (Safari5 and many non-browser clients...) |
427
|
|
|
$this->switchToProtocol('V0'); |
428
|
|
|
} |
429
|
|
|
// ---------------------------------------------------------- |
430
|
|
|
// End of protocol discovery |
431
|
|
|
// ---------------------------------------------------------- |
432
|
|
|
return true; |
433
|
|
|
} |
434
|
|
|
|
435
|
|
|
protected function switchToProtocol($proto) { |
436
|
|
|
$class = '\\PHPDaemon\\Servers\\WebSocket\\Protocols\\' . $proto; |
437
|
|
|
$conn = new $class(null, $this->pool); |
438
|
|
|
$this->pool->attach($conn); |
439
|
|
|
$conn->setFd($this->getFd(), $this->getBev()); |
440
|
|
|
$this->unsetFd(); |
441
|
|
|
$this->pool->detach($this); |
442
|
|
|
$conn->onInheritance($this); |
443
|
|
|
} |
444
|
|
|
|
445
|
|
View Code Duplication |
public function onInheritance($conn) { |
|
|
|
|
446
|
|
|
$this->server = $conn->server; |
447
|
|
|
$this->cookie = $conn->cookie; |
448
|
|
|
$this->get = $conn->get; |
449
|
|
|
$this->state = self::STATE_PREHANDSHAKE; |
450
|
|
|
$this->addr = $conn->addr; |
451
|
|
|
$this->onRead(); |
452
|
|
|
} |
453
|
|
|
|
454
|
|
|
|
455
|
|
|
/** |
456
|
|
|
* Send HTTP-status |
457
|
|
|
* @throws RequestHeadersAlreadySent |
458
|
|
|
* @param integer $code Code |
459
|
|
|
* @return boolean Success |
460
|
|
|
*/ |
461
|
|
|
public function status($code = 200) { |
|
|
|
|
462
|
|
|
return false; |
463
|
|
|
} |
464
|
|
|
|
465
|
|
|
/** |
466
|
|
|
* Send the header |
467
|
|
|
* @param string $s Header. Example: 'Location: http://php.net/' |
468
|
|
|
* @param boolean $replace Optional. Replace? |
469
|
|
|
* @param boolean $code Optional. HTTP response code |
470
|
|
|
* @throws \PHPDaemon\Request\RequestHeadersAlreadySent |
471
|
|
|
* @return boolean Success |
472
|
|
|
*/ |
473
|
|
View Code Duplication |
public function header($s, $replace = true, $code = false) { |
|
|
|
|
474
|
|
|
if ($code) { |
475
|
|
|
$this->status($code); |
|
|
|
|
476
|
|
|
} |
477
|
|
|
|
478
|
|
|
if ($this->headers_sent) { |
479
|
|
|
throw new RequestHeadersAlreadySent; |
480
|
|
|
} |
481
|
|
|
$s = strtr($s, "\r\n", ' '); |
482
|
|
|
|
483
|
|
|
$e = explode(':', $s, 2); |
484
|
|
|
|
485
|
|
|
if (!isset($e[1])) { |
486
|
|
|
$e[0] = 'STATUS'; |
487
|
|
|
|
488
|
|
|
if (strncmp($s, 'HTTP/', 5) === 0) { |
489
|
|
|
$s = substr($s, 9); |
490
|
|
|
} |
491
|
|
|
} |
492
|
|
|
|
493
|
|
|
$k = strtr(strtoupper($e[0]), Generic::$htr); |
494
|
|
|
|
495
|
|
|
if ($k === 'CONTENT_TYPE') { |
496
|
|
|
self::parse_str(strtolower($e[1]), $ctype, true); |
|
|
|
|
497
|
|
|
if (!isset($ctype['charset'])) { |
|
|
|
|
498
|
|
|
$ctype['charset'] = $this->upstream->pool->config->defaultcharset->value; |
|
|
|
|
499
|
|
|
|
500
|
|
|
$s = $e[0] . ': '; |
501
|
|
|
$i = 0; |
502
|
|
|
foreach ($ctype as $k => $v) { |
503
|
|
|
$s .= ($i > 0 ? '; ' : '') . $k . ($v !== '' ? '=' . $v : ''); |
504
|
|
|
++$i; |
505
|
|
|
} |
506
|
|
|
} |
507
|
|
|
} |
508
|
|
|
if ($k === 'SET_COOKIE') { |
509
|
|
|
$k .= '_' . ++$this->cookieNum; |
510
|
|
|
} |
511
|
|
|
elseif (!$replace && isset($this->headers[$k])) { |
512
|
|
|
return false; |
513
|
|
|
} |
514
|
|
|
|
515
|
|
|
$this->headers[$k] = $s; |
516
|
|
|
|
517
|
|
|
if ($k === 'CONTENT_LENGTH') { |
518
|
|
|
$this->contentLength = (int)$e[1]; |
519
|
|
|
} |
520
|
|
|
elseif ($k === 'LOCATION') { |
521
|
|
|
$this->status(301); |
|
|
|
|
522
|
|
|
} |
523
|
|
|
|
524
|
|
|
if (Daemon::$compatMode) { |
525
|
|
|
is_callable('header_native') ? header_native($s) : header($s); |
526
|
|
|
} |
527
|
|
|
|
528
|
|
|
return true; |
529
|
|
|
} |
530
|
|
|
} |
531
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.