/lib/Request.class.php

https://github.com/emeraldofemra/phpdaemon · PHP · 714 lines · 539 code · 0 blank · 175 comment · 132 complexity · 44f5f1698bb24b54f4038a174711bdb8 MD5 · raw file

  1. <?php
  2. /**************************************************************************/
  3. /* phpDaemon
  4. /* Web: http://github.com/kakserpom/phpdaemon
  5. /* ===========================
  6. /* @class Request
  7. /* @author kak.serpom.po.yaitsam@gmail.com
  8. /* @description Request class.
  9. /**************************************************************************/
  10. class Request
  11. {
  12. const INTERRUPT = 0;
  13. const DONE = 1;
  14. public $idAppQueue;
  15. public $mpartstate = 0;
  16. public $mpartoffset = 0;
  17. public $mpartcondisp = FALSE;
  18. public $headers = array('STATUS' => '200 OK');
  19. public $headers_sent = FALSE;
  20. public $appInstance;
  21. public $boundary = FALSE;
  22. public $aborted = FALSE;
  23. public $state = 1;
  24. public $codepoint;
  25. public $sendfp;
  26. public static $hvaltr = array(';' => '&', ' ' => '');
  27. public static $htr = array('-' => '_');
  28. public $attrs;
  29. public $shutdownFuncs = array();
  30. public $sleepuntil;
  31. public $running = FALSE;
  32. public $upstream;
  33. public $answerlen = 0;
  34. public $contentLength;
  35. /* @method __construct
  36. @description
  37. @param object Parent AppInstance.
  38. @param object Upstream.
  39. @param object Source request.
  40. @return void
  41. */
  42. public function __construct($appInstance,$upstream,$req = NULL)
  43. {
  44. if ($req === NULL) {$req = clone Daemon::$dummyRequest;}
  45. $this->appInstance = $appInstance;
  46. $this->upstream = $upstream;
  47. $this->attrs = $req->attrs;
  48. if (Daemon::$settings['expose']) {$this->header('X-Powered-By: phpDaemon/'.Daemon::$version);}
  49. $this->parseParams();
  50. $this->onWakeup();
  51. $this->init();
  52. $this->onSleep();
  53. }
  54. /* @method __toString()
  55. @description This magic method called when the object casts to string.
  56. @return string Description.
  57. */
  58. public function __toString()
  59. {
  60. return 'Request of type '.get_class($this);
  61. }
  62. /* @method chunked
  63. @description Use chunked encoding.
  64. @return void
  65. */
  66. public function chunked()
  67. {
  68. $this->header('Transfer-Encoding: chunked');
  69. $this->attrs->chunked = TRUE;
  70. }
  71. /* @method init
  72. @description Called when request constructs.
  73. @return void
  74. */
  75. public function init() {}
  76. /* @method getString
  77. @param Reference of variable.
  78. @description Gets string value from the given variable.
  79. @return string Value.
  80. */
  81. public function getString(&$var)
  82. {
  83. if (!is_string($var)) {return '';}
  84. return $var;
  85. }
  86. /* @method onWrite
  87. @description Called when the connection is ready to accept new data.
  88. @return void
  89. */
  90. public function onWrite() {}
  91. /* @method registerShutdownFunction
  92. @description Adds new callback called before the request finished.
  93. @return void
  94. */
  95. public function registerShutdownFunction($callback)
  96. {
  97. $this->shutdownFuncs[] = $callback;
  98. }
  99. /* @method unregisterShutdownFunction
  100. @description Remove the given callback.
  101. @return void
  102. */
  103. public function unregisterShutdownFunction($callback)
  104. {
  105. if (($k = array_search($callback,$this->shutdownFuncs)) !== FALSE) {$this->shutdownFuncs[] = $callback;}
  106. }
  107. /* @method codepoint
  108. @param string Name.
  109. @description Helper for easy switching between several interruptable stages of request's execution.
  110. @return boolean Execute.
  111. */
  112. public function codepoint($p)
  113. {
  114. if ($this->codepoint !== $p)
  115. {
  116. $this->codepoint = $p;
  117. return TRUE;
  118. }
  119. return FALSE;
  120. }
  121. /* @method status
  122. @throws RequestHeadersAlreadySent
  123. @param int Code
  124. @description Sends HTTP-status (200, 403, 404, 500, etc)
  125. @return void
  126. */
  127. public function status($code = 200)
  128. {
  129. if ($code === 200)
  130. {
  131. $this->header('200 OK');
  132. }
  133. elseif ($code === 404)
  134. {
  135. $this->header('404 Not Found');
  136. }
  137. elseif ($code === 403)
  138. {
  139. $this->header('404 Forbidden');
  140. }
  141. elseif ($code === 301)
  142. {
  143. $this->header('301 Moved Permonently');
  144. }
  145. elseif ($code === 302)
  146. {
  147. $this->header('302 Found');
  148. }
  149. }
  150. /* @method sleep
  151. @throws RequestSleepException
  152. @param float Time to sleep in seconds.
  153. @param boolean Set this parameter to true when use call it outside of Request->run() or if you don't want to interrupt execution now.
  154. @description Delays the request execution for the given number of seconds.
  155. @return void
  156. */
  157. public function sleep($time = 0,$set = FALSE)
  158. {
  159. if ($this->state === 0) {return;}
  160. $this->sleepuntil = microtime(TRUE)+$time;
  161. if (!$set) {throw new RequestSleepException;}
  162. $this->state = 3;
  163. }
  164. /* @method terminate
  165. @description Throws terminating exception.
  166. @return void
  167. */
  168. public function terminate($s = NULL)
  169. {
  170. if (is_string($s)) {$this->out($s);}
  171. throw new RequestTerminatedException;
  172. }
  173. /* @method wakeup
  174. @description Cancel current sleep.
  175. @return void
  176. */
  177. public function wakeup()
  178. {
  179. $this->state = 1;
  180. }
  181. /* @method call
  182. @description Called by queue dispatcher to touch the request.
  183. @return int Status.
  184. */
  185. public function call()
  186. {
  187. if ($this->state === 0)
  188. {
  189. $this->state = 1;
  190. $this->finish();
  191. return 1;
  192. }
  193. if ($this->attrs->params_done)
  194. {
  195. if (isset($this->appInstance->passphrase))
  196. {
  197. if (!isset($this->attrs->server['PASSPHRASE']) || ($this->appInstance->passphrase !== $this->attrs->server['PASSPHRASE']))
  198. {
  199. $this->state = 1;
  200. return 1;
  201. }
  202. }
  203. }
  204. if ($this->attrs->params_done && $this->attrs->stdin_done)
  205. {
  206. $this->state = 2;
  207. $this->onWakeup();
  208. try
  209. {
  210. $ret = $this->run();
  211. if ($this->state === 0) {return 1;} // Finished while running
  212. $this->state = $ret;
  213. if ($this->state === NULL) {Daemon::log('Method '.get_class($this).'::run() returned null.');}
  214. }
  215. catch (RequestSleepException $e)
  216. {
  217. $this->state = 3;
  218. }
  219. catch (RequestTerminatedException $e)
  220. {
  221. $this->state = 1;
  222. }
  223. if ($this->state === 1) {$this->finish();}
  224. $this->onSleep();
  225. return $this->state;
  226. }
  227. return 0;
  228. }
  229. /* @method onAbort
  230. @description Called when the request aborted.
  231. @return void
  232. */
  233. public function onAbort() {}
  234. /* @method onFinish
  235. @description Called when the request finished.
  236. @return void
  237. */
  238. public function onFinish() {}
  239. /* @method onWakeUp
  240. @description Called when the request wakes up.
  241. @return void
  242. */
  243. public function onWakeup()
  244. {
  245. if (!Daemon::$compatMode) {Daemon::$worker->setStatus(2);}
  246. ob_flush();
  247. $this->running = TRUE;
  248. Daemon::$req = $this;
  249. $_GET = &$this->attrs->get;
  250. $_POST = &$this->attrs->post;
  251. $_COOKIE = &$this->attrs->cookie;
  252. $_REQUEST = &$this->attrs->request;
  253. $_SESSION = &$this->attrs->session;
  254. $_FILES = &$this->attrs->files;
  255. $_SERVER = &$this->attrs->server;
  256. }
  257. /* @method onSleep
  258. @description Called when the request starts sleep.
  259. @return void
  260. */
  261. public function onSleep()
  262. {
  263. ob_flush();
  264. if (!Daemon::$compatMode) {Daemon::$worker->setStatus(1);}
  265. Daemon::$req = NULL;
  266. $this->running = FALSE;
  267. }
  268. /* @method setcookie
  269. @description Sets the cookie.
  270. @param string Name of cookie.
  271. @param string Value.
  272. @param integer. Optional. Max-Age. Default is 0.
  273. @param string. Optional. Path. Default is empty string.
  274. @param boolean. Optional. Secure. Default is false.
  275. @param boolean. Optional. HTTPOnly. Default is false.
  276. @return void
  277. @throws RequestHeadersAlreadySent
  278. */
  279. public function setcookie($name,$value = '',$maxage = 0,$path = '',$domain = '',$secure = FALSE, $HTTPOnly = FALSE)
  280. {
  281. $this->header('Set-Cookie: '.$name.'='.rawurlencode($value)
  282. .(empty($domain) ? '' : '; Domain='.$domain)
  283. .(empty($maxage) ? '' : '; Max-Age='.$maxage)
  284. .(empty($path) ? '' : '; Path='.$path)
  285. .(!$secure ? '' : '; Secure')
  286. .(!$HTTPOnly ? '' : '; HttpOnly'), false);
  287. }
  288. /* @method header
  289. @description Sets the header.
  290. @param string Header. Example: 'Location: http://php.net/'
  291. @return void
  292. @throws RequestHeadersAlreadySent
  293. */
  294. public function header($s)
  295. {
  296. if ($this->headers_sent)
  297. {
  298. throw new RequestHeadersAlreadySent();
  299. return FALSE;
  300. }
  301. $e = explode(':',$s,2);
  302. if (!isset($e[1]))
  303. {
  304. $e[0] = 'STATUS';
  305. if (strncmp($s,'HTTP/',5) === 0) {$s = substr($s,9);}
  306. }
  307. $k = strtr(strtoupper($e[0]),Request::$htr);
  308. $this->headers[$k] = $s;
  309. if ($k === 'CONTENT_LENGTH') {$this->contentLength = (int) $e[1];}
  310. if ($k === 'LOCATION') {$this->status(301);}
  311. if (Daemon::$compatMode) {header($s);}
  312. return TRUE;
  313. }
  314. /* @method parseParams
  315. @description Parses GET-query string and other request's headers.
  316. @return void
  317. */
  318. public function parseParams()
  319. {
  320. if (isset($this->attrs->server['CONTENT_TYPE']) && !isset($this->attrs->server['HTTP_CONTENT_TYPE']))
  321. {
  322. $this->attrs->server['HTTP_CONTENT_TYPE'] = $this->attrs->server['CONTENT_TYPE'];
  323. }
  324. if (isset($this->attrs->server['QUERY_STRING'])) {$this->parse_str($this->attrs->server['QUERY_STRING'],$this->attrs->get);}
  325. if (isset($this->attrs->server['REQUEST_METHOD']) && ($this->attrs->server['REQUEST_METHOD'] == 'POST') && isset($this->attrs->server['HTTP_CONTENT_TYPE']))
  326. {
  327. parse_str(strtr($this->attrs->server['HTTP_CONTENT_TYPE'],Request::$hvaltr),$contype);
  328. if (isset($contype['multipart/form-data']) && (isset($contype['boundary']))) {$this->boundary = $contype['boundary'];}
  329. }
  330. if (isset($this->attrs->server['HTTP_COOKIE'])) {$this->parse_str(strtr($this->attrs->server['HTTP_COOKIE'],Request::$hvaltr),$this->attrs->cookie);}
  331. if (isset($this->attrs->server['HTTP_AUTHORIZATION']))
  332. {
  333. $e = explode(' ',$this->attrs->server['HTTP_AUTHORIZATION'],2);
  334. if (($e[0] == 'Basic') && isset($e[1]))
  335. {
  336. $e[1] = base64_decode($e[1]);
  337. $e = explode(':',$e[1],2);
  338. if (isset($e[1])) {list($this->attrs->server['PHP_AUTH_USER'],$this->attrs->server['PHP_AUTH_PW']) = $e;}
  339. }
  340. }
  341. $this->onParsedParams();
  342. }
  343. /* @method onParsedParams
  344. @description Called when request's headers parsed.
  345. @return void
  346. */
  347. public function onParsedParams() {}
  348. /* @method combinedOut
  349. @param string String to out.
  350. @description Outputs data with headers (split by \r\n\r\n)
  351. @return boolean Success.
  352. */
  353. public function combinedOut($s)
  354. {
  355. if (!$this->headers_sent)
  356. {
  357. $e = explode("\r\n\r\n",$s,2);
  358. $h = explode("\r\n",$e[0]);
  359. foreach ($h as &$l) {$this->header($l);}
  360. if (isset($e[1])) {return $this->out($e[1]);}
  361. return TRUE;
  362. }
  363. else {return $this->out($s);}
  364. }
  365. /* @method out
  366. @param string String to out.
  367. @description Outputs data.
  368. @return boolean Success.
  369. */
  370. public function out($s,$flush = TRUE)
  371. {
  372. //Daemon::log('Output (len. '.strlen($s).', '.($this->headers_sent?'headers sent':'headers not sent').'): \''.$s.'\'');
  373. if ($flush) {ob_flush();}
  374. if ($this->aborted) {return FALSE;}
  375. $l = strlen($s);
  376. $this->answerlen += $l;
  377. if (!$this->headers_sent)
  378. {
  379. $h = isset($this->headers['STATUS'])?$this->attrs->server['SERVER_PROTOCOL'].' '.$this->headers['STATUS']."\r\n":'';
  380. if ($this->attrs->chunked) {$this->header('Transfer-Encoding: chunked');}
  381. foreach ($this->headers as $k => $line)
  382. {
  383. if ($k !== 'STATUS') {$h .= $line."\r\n";}
  384. }
  385. $h .= "\r\n";
  386. $this->headers_sent = TRUE;
  387. if (!Daemon::$compatMode)
  388. {
  389. if (!$this->attrs->chunked && !$this->sendfp) {return $this->upstream->requestOut($this,$h.$s);}
  390. $this->upstream->requestOut($this,$h);
  391. }
  392. }
  393. if ($this->attrs->chunked)
  394. {
  395. for ($o = 0; $o < $l;)
  396. {
  397. $c = min(Daemon::$parsedSettings['chunksize'],$l-$o);
  398. $chunk = dechex($c)."\r\n"
  399. .($c === $l?$s:binarySubstr($s,$o,$c)) // content
  400. ."\r\n";
  401. if ($this->sendfp) {fwrite($this->sendfp,$chunk);}
  402. else {$this->upstream->requestOut($this,$chunk);}
  403. $o += $c;
  404. }
  405. }
  406. else
  407. {
  408. if ($this->sendfp)
  409. {
  410. fwrite($this->sendfp,$s);
  411. return TRUE;
  412. }
  413. if (Daemon::$compatMode)
  414. {
  415. echo $s;
  416. return TRUE;
  417. }
  418. return $this->upstream->requestOut($this,$s);
  419. }
  420. }
  421. /* @method combinedOut
  422. @param string String to out.
  423. @description Outputs data with headers (split by \r\n\r\n)
  424. @return boolean Success.
  425. */
  426. public function headers_sent() {return $this->headers_sent;}
  427. /* @method headers_list
  428. @description Returns current list of headers.
  429. @return array Headers.
  430. */
  431. public function headers_list()
  432. {
  433. return array_values($this->headers);
  434. }
  435. /* @method parseStdin
  436. @description Parses request's body.
  437. @return void
  438. */
  439. public function parseStdin()
  440. {
  441. do
  442. {
  443. if ($this->boundary === FALSE) {break;}
  444. $continue = FALSE;
  445. if ($this->mpartstate === 0) // seek to the nearest boundary
  446. {
  447. if (($p = strpos($this->attrs->stdinbuf,$ndl = '--'.$this->boundary."\r\n",$this->mpartoffset)) !== FALSE)
  448. {
  449. // we have found the nearest boundary at position $p
  450. $this->mpartoffset = $p+strlen($ndl);
  451. $this->mpartstate = 1;
  452. $continue = TRUE;
  453. }
  454. }
  455. elseif ($this->mpartstate === 1) // parse the part's headers
  456. {
  457. $this->mpartcondisp = FALSE;
  458. if (($p = strpos($this->attrs->stdinbuf,"\r\n\r\n",$this->mpartoffset)) !== FALSE) // we got all of the headers
  459. {
  460. $h = explode("\r\n",binarySubstr($this->attrs->stdinbuf,$this->mpartoffset,$p-$this->mpartoffset));
  461. $this->mpartoffset = $p+4;
  462. $this->attrs->stdinbuf = binarySubstr($this->attrs->stdinbuf,$this->mpartoffset);
  463. $this->mpartoffset = 0;
  464. for ($i = 0, $s = sizeof($h); $i < $s; ++$i)
  465. {
  466. $e = explode(':',$h[$i],2);
  467. $e[0] = strtr(strtoupper($e[0]),Request::$htr);
  468. if (isset($e[1])) {$e[1] = ltrim($e[1]);}
  469. if (($e[0] == 'CONTENT_DISPOSITION') && isset($e[1]))
  470. {
  471. parse_str(strtr($e[1],Request::$hvaltr),$this->mpartcondisp);
  472. if (!isset($this->mpartcondisp['form-data'])) {break;}
  473. if (!isset($this->mpartcondisp['name'])) {break;}
  474. $this->mpartcondisp['name'] = trim($this->mpartcondisp['name'],'"');
  475. if (isset($this->mpartcondisp['filename']))
  476. {
  477. $this->mpartcondisp['filename'] = trim($this->mpartcondisp['filename'],'"');
  478. if (!ini_get('file_uploads')) {break;}
  479. $this->attrs->files[$this->mpartcondisp['name']] = array(
  480. 'name' => $this->mpartcondisp['filename'],
  481. 'type' => '',
  482. 'tmp_name' => '',
  483. 'error' => UPLOAD_ERR_OK,
  484. 'size' => 0,
  485. );
  486. $tmpdir = ini_get('upload_tmp_dir');
  487. if ($tmpdir === FALSE)
  488. {
  489. $this->attrs->files[$this->mpartcondisp['name']]['fp'] = FALSE;
  490. $this->attrs->files[$this->mpartcondisp['name']]['error'] = UPLOAD_ERR_NO_TMP_DIR;
  491. }
  492. else
  493. {
  494. $this->attrs->files[$this->mpartcondisp['name']]['fp'] = @fopen($this->attrs->files[$this->mpartcondisp['name']]['tmp_name'] = tempnam($tmpdir,'php'),'w');
  495. if (!$this->attrs->files[$this->mpartcondisp['name']]['fp']) {$this->attrs->files[$this->mpartcondisp['name']]['error'] = UPLOAD_ERR_CANT_WRITE;}
  496. }
  497. $this->mpartstate = 3;
  498. }
  499. else
  500. {
  501. $this->attrs->post[$this->mpartcondisp['name']] = '';
  502. }
  503. }
  504. elseif (($e[0] == 'CONTENT_TYPE') && isset($e[1]))
  505. {
  506. if (isset($this->mpartcondisp['name']) && isset($this->mpartcondisp['filename']))
  507. {
  508. $this->attrs->files[$this->mpartcondisp['name']]['type'] = $e[1];
  509. }
  510. }
  511. }
  512. if ($this->mpartstate === 1) {$this->mpartstate = 2;}
  513. $continue = TRUE;
  514. }
  515. }
  516. elseif (($this->mpartstate === 2) || ($this->mpartstate === 3)) // process the body
  517. {
  518. if ((($p = strpos($this->attrs->stdinbuf,$ndl = "\r\n--".$this->boundary."\r\n",$this->mpartoffset)) !== FALSE)
  519. || (($p = strpos($this->attrs->stdinbuf,$ndl = "\r\n--".$this->boundary."--\r\n",$this->mpartoffset)) !== FALSE)
  520. )
  521. {
  522. if (($this->mpartstate === 2) && isset($this->mpartcondisp['name'])) {$this->attrs->post[$this->mpartcondisp['name']] .= binarySubstr($this->attrs->stdinbuf,$this->mpartoffset,$p-$this->mpartoffset);}
  523. elseif (($this->mpartstate === 3) && isset($this->mpartcondisp['filename']))
  524. {
  525. if ($this->attrs->files[$this->mpartcondisp['name']]['fp']) {fwrite($this->attrs->files[$this->mpartcondisp['name']]['fp'],binarySubstr($this->attrs->stdinbuf,$this->mpartoffset,$p-$this->mpartoffset));}
  526. $this->attrs->files[$this->mpartcondisp['name']]['size'] += $p-$this->mpartoffset;
  527. }
  528. if ($ndl === "\r\n--".$this->boundary."--\r\n")
  529. {
  530. $this->mpartoffset = $p+strlen($ndl);
  531. $this->mpartstate = 0; // we done at all
  532. }
  533. else
  534. {
  535. $this->mpartoffset = $p;
  536. $this->mpartstate = 1; // let us parse the next part
  537. $continue = TRUE;
  538. }
  539. $this->attrs->stdinbuf = binarySubstr($this->attrs->stdinbuf,$this->mpartoffset);
  540. $this->mpartoffset = 0;
  541. }
  542. else
  543. {
  544. $p = strrpos($this->attrs->stdinbuf,"\r\n",$this->mpartoffset);
  545. if ($p !== FALSE)
  546. {
  547. if (($this->mpartstate === 2) && isset($this->mpartcondisp['name'])) {$this->attrs->post[$this->mpartcondisp['name']] .= binarySubstr($this->attrs->stdinbuf,$this->mpartoffset,$p-$this->mpartoffset);}
  548. elseif (($this->mpartstate === 3) && isset($this->mpartcondisp['filename']))
  549. {
  550. if ($this->attrs->files[$this->mpartcondisp['name']]['fp']) {fwrite($this->attrs->files[$this->mpartcondisp['name']]['fp'],binarySubstr($this->attrs->stdinbuf,$this->mpartoffset,$p-$this->mpartoffset));}
  551. $this->attrs->files[$this->mpartcondisp['name']]['size'] += $p-$this->mpartoffset;
  552. if (Daemon::parseSize(ini_get('upload_max_filesize')) < $this->attrs->files[$this->mpartcondisp['name']]['size'])
  553. {
  554. $this->attrs->files[$this->mpartcondisp['name']]['error'] = UPLOAD_ERR_INI_SIZE;
  555. }
  556. if (isset($this->attrs->post['MAX_FILE_SIZE']) && ($this->attrs->post['MAX_FILE_SIZE'] < $this->attrs->files[$this->mpartcondisp['name']]['size']))
  557. {
  558. $this->attrs->files[$this->mpartcondisp['name']]['error'] = UPLOAD_ERR_FORM_SIZE;
  559. }
  560. }
  561. $this->mpartoffset = $p;
  562. $this->attrs->stdinbuf = binarySubstr($this->attrs->stdinbuf,$this->mpartoffset);
  563. $this->mpartoffset = 0;
  564. }
  565. }
  566. }
  567. }
  568. while ($continue);
  569. }
  570. /* @method abort
  571. @description Aborts the request.
  572. @return void
  573. */
  574. public function abort()
  575. {
  576. if ($this->aborted) {return;}
  577. $this->aborted = TRUE;
  578. $this->onWakeup();
  579. $this->onAbort();
  580. if ((ignore_user_abort() === 1) && ($this->state > 1) && !Daemon::$compatMode)
  581. {
  582. if (!Daemon::$parsedSettings['keepalive']) {$this->upstream->closeConnection($this->attrs->connId);}
  583. }
  584. else
  585. {
  586. $this->finish(-1);
  587. }
  588. $this->onSleep();
  589. }
  590. /* @method postPrepare
  591. @description Prepares the request's body.
  592. @return void
  593. */
  594. public function postPrepare()
  595. {
  596. if (isset($this->attrs->server['REQUEST_METHOD']) && ($this->attrs->server['REQUEST_METHOD'] == 'POST'))
  597. {
  598. if ($this->boundary === FALSE) {$this->parse_str($this->attrs->stdinbuf,$this->attrs->post);}
  599. if (isset($this->attrs->server['REQUEST_BODY_FILE']) && Daemon::$settings['autoreadbodyfile'])
  600. {
  601. $this->readBodyFile();
  602. }
  603. }
  604. }
  605. /* @method stdin
  606. @param string Piece of request's body.
  607. @description Called when new piece of request's body is received.
  608. @return void
  609. */
  610. public function stdin($c)
  611. {
  612. if ($c !== '')
  613. {
  614. $this->attrs->stdinbuf .= $c;
  615. $this->attrs->stdinlen += strlen($c);
  616. }
  617. if (!isset($this->attrs->server['HTTP_CONTENT_LENGTH']) || ($this->attrs->server['HTTP_CONTENT_LENGTH'] <= $this->attrs->stdinlen))
  618. {
  619. $this->attrs->stdin_done = TRUE;
  620. $this->postPrepare();
  621. }
  622. $this->parseStdin();
  623. }
  624. /* @method finish
  625. @param integer Optional. Status. 0 - normal, -1 - abort, -2 - termination
  626. @param boolean Optional. Zombie. Default is false.
  627. @description Finishes the request.
  628. @return void
  629. */
  630. public function finish($status = 0,$zombie = FALSE)
  631. {
  632. if ($this->state === 0) {return;}
  633. if (!$zombie) {$this->state = 0;}
  634. if (!($r = $this->running))
  635. {
  636. $this->onWakeup();
  637. }
  638. while (($c = array_shift($this->shutdownFuncs)) !== NULL) {call_user_func($c,$this);}
  639. if (!$r) {$this->onSleep();}
  640. $this->onFinish();
  641. if (Daemon::$compatMode) {return;}
  642. if ((Daemon::$parsedSettings['autogc'] > 0) && (Daemon::$worker->queryCounter > 0) && (Daemon::$worker->queryCounter % Daemon::$parsedSettings['autogc'] === 0))
  643. {
  644. gc_collect_cycles();
  645. }
  646. if (Daemon::$compatMode) {return;}
  647. ob_flush();
  648. if ($status !== -1)
  649. {
  650. if (!$this->headers_sent) {$this->out('');}
  651. // $status: 0 - FCGI_REQUEST_COMPLETE, 1 - FCGI_CANT_MPX_CONN, 2 - FCGI_OVERLOADED, 3 - FCGI_UNKNOWN_ROLE
  652. $appStatus = 0;
  653. $this->upstream->endRequest($this,$appStatus,$status);
  654. if ($this->sendfp) {fclose($this->sendfp);}
  655. if (isset($this->attrs->files))
  656. {
  657. foreach ($this->attrs->files as &$f)
  658. {
  659. if (($f['error'] === UPLOAD_ERR_OK) && file_exists($f['tmp_name']))
  660. {
  661. unlink($f['tmp_name']);
  662. }
  663. }
  664. }
  665. if (isset($this->attrs->session)) {session_commit();}
  666. }
  667. }
  668. /* @method readBodyFile
  669. @description Reads request's body from file.
  670. @return void
  671. */
  672. public function readBodyFile()
  673. {
  674. if (!isset($this->attrs->server['REQUEST_BODY_FILE'])) {return FALSE;}
  675. $fp = fopen($this->attrs->server['REQUEST_BODY_FILE'],'rb');
  676. if (!$fp)
  677. {
  678. Daemon::log('Couldn\'t open request-body file \''.$this->attrs->server['REQUEST_BODY_FILE'].'\' (REQUEST_BODY_FILE).');
  679. return FALSE;
  680. }
  681. while (!feof($fp))
  682. {
  683. $this->stdin($this->fread($fp,4096));
  684. }
  685. fclose($fp);
  686. $this->attrs->stdin_done = TRUE;
  687. }
  688. /* @method parse_str
  689. @param string String to parse.
  690. @param array Reference to the resulting array.
  691. @description Replacement for default parse_str(), it supoorts UCS-2 like this: %uXXXX.
  692. @return void
  693. */
  694. public function parse_str($s,&$array)
  695. {
  696. if ((stripos($s,'%u') !== FALSE) && preg_match('~(%u[a-f\d]{4}|%[c-f][a-f\d](?!%[89a-f][a-f\d]))~is',$s,$m))
  697. {
  698. $s = preg_replace_callback('~%(u[a-f\d]{4}|[a-f\d]{2})~i',array($this,'parse_str_callback'),$s);
  699. }
  700. parse_str($s,$array);
  701. }
  702. /* @method parse_str_callback
  703. @param array Match.
  704. @description Called in preg_replace_callback in parse_str.
  705. @return string Replacement.
  706. */
  707. public function parse_str_callback($m)
  708. {
  709. return urlencode(html_entity_decode('&#'.hexdec($m[1]).';',ENT_NOQUOTES,'utf-8'));
  710. }
  711. }
  712. class RequestSleepException extends Exception {}
  713. class RequestTerminatedException extends Exception {}
  714. class RequestHeadersAlreadySent extends Exception {}