0) { $cookie[$cookies[$i]] = ""; } else { printf("WARNING: malformat cookie element [%s]\n", $cookies[$i]); } continue; } $value = mb_substr($cookies[$i], mb_strlen($name)+1, 10140, 'UTF-8'); $cookie[$name] = urldecode($value); } } // GET params management $get_vars = explode('?', $req[1], 2); $path = $get_vars[0]; if (count($get_vars) > 1) { $a = explode('&', $get_vars[1]); // printf("A COUNT: [%s] %d\n", $a[0], count($a)); for ($i = 0 ; $i < count($a) ; $i++) { $b = explode('=', $a[$i]); if ($b[0] == "") continue; $get[$b[0]] = urldecode($b[1]); } } // POST params management if ($req[0] == 'POST') { $conttype_all = explode(";", $header['Content-Type']); $header['Content-Type'] = $conttype_all[0]; // $path_all[1-] other things like charset and so on // if (content-type is wrong || content-length isn't set) // return false if ($header['Content-Type'] != 'application/x-www-form-urlencoded' || !isset($header['Content-Length'])) { return FALSE; } $post_len = mb_strlen($line, "ASCII"); // printf("INFO: postlen: %d\n", $post_len); $rest = (int)($header['Content-Length']) - $post_len; if ($rest == 0) { post_manage($post, $line); } else { $cont = $line; } } break; } if ($line == "") { $check_post = TRUE; continue; } $split = explode(":", $line, 2); $hea_id = trim(mb_convert_case($split[0], MB_CASE_TITLE, 'UTF-8')); $header[$hea_id] = $split[1]; } return $path; } function gpcs_var($name, $get, $post, $cookie) { if (isset($GLOBALS[$name])) return FALSE; else if (isset($cookie[$name])) return ($cookie[$name]); else if (isset($post[$name])) return ($post[$name]); else if (isset($get[$name])) return ($get[$name]); return FALSE; } function headers_render($header, $len) { $cookies = ""; if (isset($header['cookies'])) { $cookies = $header['cookies']->render(); unset($header['cookies']); } if (isset($header['Location'])) { $s = sprintf("HTTP/1.1 302 OK\r\n%sLocation: %s\r\n", $cookies, $header['Location']); } else if (isset($header['HTTP-Response'])) { $s = sprintf("HTTP/1.1 %s\r\n", $header['HTTP-Response']); foreach($header as $key => $value) { if (strtolower($key) == "http-response") continue; $s .= sprintf("%s: %s\r\n", $key, $value); } if ($len >= 0) { $s .= sprintf("Content-Length: %ld\r\n", $len); } } else { $s = "HTTP/1.1 200 OK\r\n"; if (!isset($header['Date'])) $s .= sprintf("Date: %s\r\n", date(DATE_RFC822)); if (!isset($header['Connection'])) $s .= "Connection: close\r\n"; if (!isset($header['Content-Type'])) $s .= "Content-Type: text/html\r\n"; foreach($header as $key => $value) { $s .= sprintf("%s: %s\r\n", $key, $value); } if ($len >= 0) { $s .= sprintf("Content-Length: %d\r\n", $len); } else { $s .= "Cache-Control: no-cache, must-revalidate\r\n"; $s .= "Expires: Mon, 26 Jul 1997 05:00:00 GMT\r\n"; if (!isset($header['Content-Encoding'])) { $s .= "Content-Encoding: chunked\r\n"; } $s .= "Transfer-Encoding: chunked\r\n"; } $s .= $cookies; } $s .= "\r\n"; return ($s); } /* * Caching system using ob php system to cache old style pages * to a var and than send it with more calm */ function shutta() { log_rd2("SHUTTA [".connection_status()."] !"); } register_shutdown_function('shutta'); /* * MAIN */ function get_encoding($header) { $enc = "plain"; if (isset($header['Accept-Encoding'])) { $acc = explode(',', $header['Accept-Encoding']); if (array_search('gzip', $acc) !== FALSE) { $enc = 'gzip'; } else if (array_search('deflate', $acc) !== FALSE) { $enc = 'deflate'; } } return ($enc); } class Cookie { var $attr; // Set-Cookie: reg_fb_gate=deleted; Expires=Thu, 01-Jan-1970 00:00:01 GMT; Path=/; Domain=.foo.com; HttpOnly // string $name [, string $value [, int $expire = 0 [, string $path [, string $domain [, bool $secure = false [, bool $httponly = false ]]]]]] ) function Cookie() { $this->attr = array(); } static function create($name) { $thiz = new Cookie(); $thiz->attr[$name] = ""; $argc = func_num_args(); for ($i = 1 ; $i < $argc ; $i++) { $arg = func_get_arg($i); switch ($i) { case 1: $thiz->attr[$name] = urlencode($arg); break; case 2: $thiz->attr['Expires'] = gmdate('D, d M Y H:i:s \G\M\T', $arg); // RFC 1211 format break; case 3: $thiz->attr['Path'] = $arg; break; case 4: $thiz->attr['Domain'] = $arg; break; case 5: if ($arg == TRUE) { $thiz->attr['Secure'] = NULL; } break; case 6: if ($arg == TRUE) { $thiz->attr['HttpOnly'] = NULL; } break; default: return FALSE; } } return $thiz; } function render() { $r = "Set-Cookie: "; $isfirst = TRUE; foreach ($this->attr as $k => $v) { if ($v == NULL) { $r .= sprintf("%s%s", ($isfirst ? "" : "; "), $k); } else { $r .= sprintf("%s%s=%s", ($isfirst ? "" : "; "), $k, $v); } $isfirst = FALSE; } $r .= "\r\n"; return $r; } } class Cookies { var $cookies; function Cookies() { $this->cookies = array(); } function add($name) { if (($cookie = call_user_func_array("Cookie::create", func_get_args())) == FALSE) return (FALSE); array_push($this->cookies, $cookie); return (TRUE); } function render() { $r = ""; foreach ($this->cookies as $cookie) { $r .= $cookie->render(); } return ($r); } } class Sac_a_push { // maybe fixed_fd is unuseful static $fixed_fd = 3; static $cnt_master = NULL; static $cnt_slave = NULL; var $provider_proxy; // list of provider/browser that offer proxy service var $file_socket_pfx; var $unix_socket_pfx; var $direct_socket; // socket where read direct commands var $socks; var $s2u; // user associated with input socket var $s2p; // pending page associated with input socket var $pending_pages; var $is_daemon; var $list_web; var $list_cmd; var $in; var $debug; var $blocking_mode; var $app; var $curtime; var $rndstr; var $main_loop; function Sac_a_push() { } function sig_handler($sig) { switch ($sig) { case SIGINT: exit(1); break; case SIGTERM: if (static::$cnt_master != NULL) { fwrite(static::$cnt_master, "\nshutdown\n"); fflush(static::$cnt_master); } else { exit(1); } break; case SIGHUP: if (static::$cnt_master != NULL) { fwrite(static::$cnt_master, "\nreload\n"); fflush(static::$cnt_master); } break; } } static function create(&$app, $sockname_pfx, $debug, $blocking_mode, $provider_proxy, $argv) { $thiz = new Sac_a_push(); $thiz->app = $app; $thiz->provider_proxy = ProviderProxy::create(); $thiz->file_socket_pfx = $sockname_pfx; $thiz->unix_socket_pfx = "unix://$sockname_pfx"; $thiz->direct_socket = "unix://${sockname}_admin.sock"; $thiz->debug = $debug; $thiz->list_web = array(); $thiz->socks = array(); $thiz->s2u = array(); $thiz->s2p = array(); $thiz->pending_pages = array(); $thiz->is_daemon = FALSE; if (array_search("-d", $argv) !== FALSE || array_search("--daemon", $argv) !== FALSE) { $thiz->is_daemon = TRUE; } // create a couple of sockets for control management if (($sockpair = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, STREAM_IPPROTO_IP)) == FALSE) { return FALSE; } static::$cnt_master = $sockpair[0]; static::$cnt_slave = $sockpair[1]; pcntl_signal(SIGTERM, array("Sac_a_push", "sig_handler")); pcntl_signal(SIGINT, array("Sac_a_push", "sig_handler")); pcntl_signal(SIGHUP, array("Sac_a_push", "sig_handler")); $thiz->blocking_mode = 0; // 0 for non-blocking $thiz->rndstr = ""; for ($i = 0 ; $i < 4096 ; $i++) { if (($i % 128) == 0) $thiz->rndstr .= " "; else $thiz->rndstr .= chr(mt_rand(65, 90)); } for ($i = 0 ; $i < USOCK_POOL_N ; $i++) { $file_socket = $thiz->file_socket_pfx . sprintf("%d.sock", $i); if (file_exists($file_socket)) { unlink($file_socket); } } $file_socket_admin = $thiz->file_socket_pfx . "_admin.sock"; if (file_exists($file_socket_admin)) { unlink($file_socket_admin); } $old_umask = umask(0); for ($i = 0 ; $i < USOCK_POOL_N ; $i++) { $unix_socket = sprintf("%s%d.sock", $thiz->unix_socket_pfx, $i); if (($list_sock = stream_socket_server($unix_socket, $err, $errs)) === FALSE) { return (FALSE); } array_push($thiz->list_web, $list_sock); } if (($thiz->list_cmd = stream_socket_server($thiz->direct_socket, $err, $errs)) === FALSE) { return (FALSE); } umask($old_umask); for ($i = 0 ; $i < USOCK_POOL_N ; $i++) { stream_set_blocking($thiz->list_web[$i], $thiz->blocking_mode); # Set the stream to non-blocking } stream_set_blocking($thiz->list_cmd, $thiz->blocking_mode); # Set the stream to non-blocking if (($thiz->in = fopen("php://stdin", "r")) === FALSE) { return(FALSE); } $thiz->main_loop = FALSE; $thiz->reload(TRUE, $provider_proxy); return ($thiz); } function socks_set($sock, $user, $pendpage) { $id = intval($sock); $this->socks[$id] = $sock; if ($user != NULL) $this->s2u[$id] = $user; if ($pendpage != NULL) $this->s2p[$id] = $pendpage; } function socks_unset($sock) { $id = intval($sock); if (isset($this->s2u[$id])) unset($this->s2u[$id]); if (isset($this->s2p[$id])) unset($this->s2p[$id]); unset($this->socks[$id]); } function pendpage_try_addcont(&$new_socket, $tout, $method, $header, $get, $post, $cookie, $path, $addr, $rest, $cont) { $pendpage = PendingPage::pendingpage_continue( $new_socket, $this->curtime, $tout, $method, $header, $get, $post, $cookie, $path, $addr, $rest, $cont); $pendpage->try_flush($this->curtime); // Add $pendpage to the pendpage array (in any case) // fprintf(STDERR, "IMPORTANT: Pendadd: %d\n", $pendpage->status); $this->pendpage_add($pendpage); } function pendpage_try_addflush(&$new_socket, $tout, $enc, $header_out, $content) { $pendpage = PendingPage::pendingpage_flushing($new_socket, $this->curtime, $tout, $enc, $header_out, $content); if ($pendpage->try_flush($this->curtime) == FALSE) { // Add $pendpage to the pendpage array $this->pendpage_add($pendpage); } } function pendpage_add($pendpage) { array_push($this->pending_pages, $pendpage); $this->socks_set($pendpage->socket_get(), NULL, $pendpage); } function pendpage_rem($pendpage) { $sock = $pendpage->socket_get(); if (($key = array_search($pendpage, $this->pending_pages)) !== FALSE) { unset($this->pending_pages[$key]); } else { fprintf(STDERR, "WARNING: pendpage not found\n"); } $this->socks_unset($sock); fprintf(STDERR, "PP_REM: %d\n", intval($sock)); } function pendpage_try_addwait(&$new_socket, $tout, $method, $header, $get, $post, $cookie, $path, $addr, $rest, $cont) { $pendpage = PendingPage::pendingpage_waiting($new_socket, $this->curtime, $tout, $method, $header, $get, $post, $cookie, $path, $addr, $rest, $cont); /* if ($pendpage->try_flush($this->curtime) == FALSE) { // Add $pendpage to the pendpage array */ $this->pendpage_add($pendpage); /* } */ } function garbage_manager($force) { $this->app->garbage_manager($force); foreach ($this->socks as $k => $sock) { $id = intval($sock); if (isset($this->s2u[$id])) { $user = $this->s2u[$id]; if ($user->the_end) { if (($user->rd_toflush == FALSE && $user->rd_step == $user->step) || $user->rd_endtime_is_expired($this->curtime)) { if ($user->rd_socket_get() != NULL) { $user->rd_socket_set(NULL); } unset($this->socks[$id]); unset($this->s2u[$id]); fclose($sock); // printf("CLOSE ON GARBAGE MANAGER\n"); } } } } $this->app->users_cleanup(); } function check_globals() { GLOBAL $_globals_list; foreach ($_globals_list as $g) { if (!array_search($g, $GLOBALS) || !isset($GLOBALS[$g])) { error_log(sprintf("Global [%s] not declared", $g)); return FALSE; } } return TRUE; } function run() { GLOBAL $DOCUMENT_ROOT, $HTTP_HOST; GLOBAL $G_alarm_passwd, $G_ban_list, $G_black_list, $G_cloud_smasher, $G_provider_proxy; GLOBAL $G_btrace_pref_sub, $G_dbauth; GLOBAL $G_dbpfx, $G_donors_all, $G_donors_cur, $G_is_local, $G_lang; GLOBAL $G_poll_entries, $G_poll_name, $G_poll_title, $G_proxy_white_list; GLOBAL $G_room_roadmap, $G_shutdown; GLOBAL $G_splash_content, $G_splash_contents, $G_splash_cont_idx; GLOBAL $G_splash_h, $G_splash_idx, $G_splash_interval, $G_splash_timeout; GLOBAL $G_splash_w, $G_topbanner, $G_with_donors, $G_with_poll; GLOBAL $G_with_splash, $G_sidebanner, $G_sidebanner_idx; GLOBAL $G_with_topbanner; GLOBAL $G_tos_vers, $G_tos_fname, $G_tos_dtsoft, $G_tos_dthard, $G_tos_idx, $G_doc_path; if (!$this->check_globals()) { fprintf(STDERR, "Take a look to the phplog file, GLOBALS missing!\n"); sleep(10); } if ($this->main_loop) { return (FALSE); } $this->main_loop = TRUE; $list_web_arr = array(); for ($i = 0 ; $i < USOCK_POOL_N ; $i++) { $list_web_arr[intval($this->list_web[$i])] = $this->list_web[$i]; } $lastime = 0; $dump_users = TRUE; while ($this->main_loop) { $this->app->sess_cur_set(FALSE); $this->curtime = time(); if ($lastime != ($this->curtime >> 2)) { fprintf(STDERR, "\nIN LOOP: Current opened: %d pending_pages: %d\n", count($this->socks), count($this->pending_pages)); } /* Prepare the read array */ /* // when we manage it ... */ /* if ($shutdown) */ /* $read = array_merge(array("$in" => $in), $socks); */ /* else */ $pre_read = array_merge($list_web_arr, array( intval($this->list_cmd) => $this->list_cmd, intval(static::$cnt_slave) => static::$cnt_slave), $this->socks); if ($this->is_daemon == FALSE) { $read = array_merge($pre_read, array(intval($this->in) => $this->in)); } else { $read = $pre_read; } if ($this->debug > 1) { printf("PRE_SELECT\n"); print_r($read); } $write = NULL; $except = NULL; $num_changed_sockets = @stream_select($read, $write, $except, 0, 500000); if ($num_changed_sockets == 0) { // printf(" no data in 5 secs, splash [%d]\n", $G_with_splash); ; } else if ($num_changed_sockets > 0) { if ($lastime != ($this->curtime >> 2)) { printf("num sock %d num_of_socket: %d\n", $num_changed_sockets, count($read)); } if ($this->debug > 1) { print_r($read); } /* At least at one of the sockets something interesting happened */ foreach ($read as $i => $sock) { $id = intval($sock); $manage_page = FALSE; /* is_resource check is required because there is the possibility that during new request an old connection is closed */ if (!is_resource($sock)) { continue; } $list_web_idx = array_search($sock, $this->list_web, TRUE); if ($list_web_idx !== FALSE) { $list_web_usock = $this->list_web[$list_web_idx]; // printf("NUOVA CONNEX\n"); if (($new_unix = stream_socket_accept($list_web_usock)) == FALSE) { printf("SOCKET_ACCEPT FAILED\n"); continue; } $stream_info = ""; $method = ""; $get = array(); $post = array(); $cookie = array(); $rest = 0; $cont = ""; if (($new_socket = ancillary_getstream($new_unix, $stream_info)) !== FALSE) { // printf("NEW_SOCKET: %d\n", intval($new_socket)); stream_set_blocking($new_socket, $this->blocking_mode); // Set the stream to non-blocking // error_log(sprintf("RECEIVED HEADER:\n%s", $stream_info)); if (($path = spu_process_info($stream_info, $method, $header, $get, $post, $cookie, $rest, $cont)) == FALSE) { fprintf(STDERR, "TODO: fix wrong header management\n"); } // We try to get real IP from header (passed by proxy) and then fallback to direct connection IP // error_log(sprintf("addr: [%s]", $addr)); // error_log(sprintf("X-Real-Ip: [%s]", array_key_exists('X-Real-Ip', $header) ? $header['X-Real-Ip'] : "Not exists")); if (array_key_exists('X-Real-Ip', $header)) { $addr = $header['X-Real-Ip']; } else { $addr = addrtoipv4(stream_socket_get_name($new_socket, TRUE)); } // FOR TEST $header['X-Forwarded-For'] = '154.155.22.33'; $addr = $this->pproxy_realip($header, $addr); // printf("PATH: [%s] [%s]\n", $path, print_r($header, TRUE)); if ($method == "POST" && $rest > 0) { if (isset($header['Expect']) && $header['Expect'] == '100-continue') { // fprintf(STDERR, "\nPOSTA DE CHE\n\n"); $this->pendpage_try_addcont($new_socket, 20, $method, $header, $get, $post, $cookie, $path, $addr, $rest, $cont); } else { $this->pendpage_try_addwait($new_socket, 20, $method, $header, $get, $post, $cookie, $path, $addr, $rest, $cont); } } else { $manage_page = TRUE; } // printf("number of sockets after %d\n", count($this->socks)); } else { printf("WARNING: ancillary_getstream failed\n"); } } else if ($sock === $this->list_cmd) { // printf("NUOVA DIRECT CONNEX\n"); if (($new_unix = stream_socket_accept($this->list_cmd)) == FALSE) { printf("SOCKET_ACCEPT FAILED\n"); continue; } stream_set_blocking($new_unix, $this->blocking_mode); $this->direct_mgmt($new_unix); } // not socket_list nor socket_list_cmd else { // already opened socket $buf = fread($sock, 4096); // if socket is closed if ($buf == FALSE || feof($sock)) { // close socket case if ($buf == FALSE) { // printf("INFO: read return false\n"); ; } if (array_search($sock, $this->list_web, TRUE) !== FALSE) { // printf("Arrivati %d bytes da list\n", mb_strlen($buf, "ASCII")); return(21); } else if ($sock === $this->list_cmd) { // printf("Arrivati %d bytes da list_cmd\n", mb_strlen($buf, "ASCII")); return(23); } else if ($sock === $this->in || $sock === static::$cnt_slave) { // printf("Arrivati %d bytes da stdin\n", mb_strlen($buf, "ASCII")); return(22); } else { unset($this->socks[$id]); if (isset($this->s2u[$id])) { // $user_a[$s2u[$id]]->disable(); if ($this->s2u[$id]->rd_socket_get() != NULL) { // try to send close frame (for websocket) $clo = $this->s2u[$id]->stream_close(); $clo_l = mb_strlen($clo, "ASCII"); @fwrite($sock, $clo, $clo_l); $this->s2u[$id]->rd_socket_set(NULL); } unset($this->s2u[$id]); } } fclose($sock); // printf("CLOSE ON READ\n"); if ($this->debug > 1) { printf("post unset\n"); print_r($this->socks); } } // if ($buf == FALSE || mb_strlen($buf, "ASCII") == 0) { else { // data on the socket if ($this->debug > 1) { print_r($read); } if (array_search($sock, $this->list_web, TRUE) !== FALSE) { // printf("Arrivati %d bytes da list\n", mb_strlen($buf, "ASCII")); ; } else if ($sock === $this->list_cmd) { // printf("Arrivati %d bytes da list_cmd\n", mb_strlen($buf, "ASCII")); ; } else if ($sock === $this->in || $sock === static::$cnt_slave) { // printf("Arrivati %d bytes da stdin\n", mb_strlen($buf, "ASCII")); $line = trim($buf); if ($line == "reload") { require("$DOCUMENT_ROOT/Etc/".BRISK_CONF); $this->reload(FALSE, $G_provider_proxy); $this->app->reload(FALSE, $G_ban_list, $G_black_list, $G_cloud_smasher); if (!$this->check_globals()) { fprintf(STDERR, "Take a look to the phplog file, GLOBALS missing!\n"); sleep(10); } global_dump(); } else if ($line == "dump") { $dump_users = TRUE; } else if ($line == "shutdown" || $line == "sd") { if ($this->app->dump_data()) { return(0); } else { return(1); } } } else { // data arrived from not special socket $key = array_search("$sock", $this->socks); // fprintf(STDERR, "Arrivati %d bytes dalla socket n. %d\n", mb_strlen($buf, "ASCII"), $key); if (isset($this->s2u[$id])) { // // TODO: // fix $addr // fix $this->pendpage_try_addflush below (probably not required) // $addr = "127.0.0.1"; $user = $this->s2u[$id]; // fprintf(STDERR, 'POST USER'); if ($user && $user->rd_transp && strpos($user->rd_transp->type, "websocket") !== FALSE) { $clie_cmd = $user->rd_transp->unchunk($buf); $clie_cmd = json_decode($clie_cmd, TRUE); // fprintf(STDERR, "HERE WE ARE INCOMING DATA [%s]\n", print_r($clie_cmd, TRUE)); $wr_addr = substr(parse_url($clie_cmd["target"])["path"], strlen(SITE_PREFIX)); if ($wr_addr == "index_wr.php") { ob_start(); // complete: index_wr_main($this->app, $addr, $get, $post, $cookie); index_wr_main($this->app, $addr, $clie_cmd, NULL, NULL); $content = ob_get_contents(); ob_end_clean(); } else if ($wr_addr == "briskin5/index_wr.php") { $table_idx = $clie_cmd['table_idx']; $table_token = $clie_cmd['table_token']; if (($bri = $this->app->match_get($table_idx, $table_token)) != FALSE) { ob_start(); bin5_index_wr_main($bri, $addr, $clie_cmd, NULL, NULL); $content = ob_get_contents(); ob_end_clean(); } } /* briskin5/index_wr.php if (isset($table_idx) && isset($table_token)) { if (($bri = $s_a_p->app->match_get($table_idx, $table_token)) != FALSE) { ob_start(); bin5_index_wr_main($bri, $addr, $get, $post, $cookie); $content = ob_get_contents(); ob_end_clean(); } else { $content = "Bin5 Load data error"; } } else { $content = "Bin5 Load data error"; } $s_a_p->pendpage_try_addflush($new_socket, 20, $enc, $header_out, $content); */ } } else { fprintf(STDERR, "User associated with ID: %s not found\n", $id); } if (isset($this->s2p[$id])) { $this->s2p[$id]->rest -= mb_strlen($buf, "ASCII"); $this->s2p[$id]->cont .= $buf; if ($this->s2p[$id]->rest <= 0) { $header = $new_socket = $path = $addr = $get = $cookie = 0; $post = array(); $this->s2p[$id]->context_get($header, $new_socket, $path, $addr, $get, $post, $cookie); $this->pendpage_rem($this->s2p[$id]); fprintf(STDERR, "SOCKET RUN: %s\n", $new_socket); $manage_page = TRUE; } } } } } if ($manage_page == TRUE) { /* printf("M: %s\nHEADER:\n", $method); print_r($header); printf("GET:\n"); print_r($get); printf("POST:\n"); print_r($post); printf("COOKIE:\n"); print_r($cookie); */ $header_out = array(); // TODO: MOVE DOWN request_mgr to factorize new_sockets and POST closed $rret = FALSE; if (!strncmp($path, SITE_PREFIX, SITE_PREFIX_LEN)) { $rret = $this->app->request_mgr($this, $header, $header_out, $new_socket, substr($path, SITE_PREFIX_LEN), $addr, $get, $post, $cookie); } if ($rret == FALSE) { // FIXME: manage 404 !!! printf("TODO: fix unknown page\n"); fclose($new_socket); } } } } $this->garbage_manager(FALSE); /* manage unfinished pages */ foreach ($this->pending_pages as $k => $pendpage) { // TODO: try_flush if exists in the class if ($pendpage->try_flush($this->curtime) == TRUE) { unset($this->pending_pages[$k]); } } /* $response: raw stream data not sent $content: html consistent data (