/** * Render template * @return string * @param $file string * @param $mime string * @param $hive array * @param $ttl int **/ function render($file, $mime = 'text/html', array $hive = NULL, $ttl = 0) { $fw = Base::instance(); $cache = Cache::instance(); if (!is_dir($tmp = $fw->get('TEMP'))) { mkdir($tmp, Base::MODE, TRUE); } foreach ($fw->split($fw->get('UI')) as $dir) { $cached = $cache->exists($hash = $fw->hash($dir . $file), $data); if ($cached && $cached[0] + $ttl > microtime(TRUE)) { return $data; } if (is_file($view = $fw->fixslashes($dir . $file))) { if (!is_file($this->view = $tmp . $fw->hash($fw->get('ROOT') . $fw->get('BASE')) . '.' . $fw->hash($view) . '.php') || filemtime($this->view) < filemtime($view)) { // Remove PHP code and comments $text = preg_replace('/(?<!["\'])\\h*<\\?(?:php|\\s*=).+?\\?>\\h*' . '(?!["\'])|\\{\\*.+?\\*\\}/is', '', $fw->read($view)); $text = $this->parse($text); $fw->write($this->view, $this->build($text)); } if (isset($_COOKIE[session_name()])) { @session_start(); } $fw->sync('SESSION'); if ($mime && PHP_SAPI != 'cli' && !headers_sent()) { header('Content-Type: ' . ($this->mime = $mime) . '; ' . 'charset=' . $fw->get('ENCODING')); } $data = $this->sandbox($hive); if (isset($this->trigger['afterrender'])) { foreach ($this->trigger['afterrender'] as $func) { $data = $fw->call($func, $data); } } if ($ttl) { $cache->set($hash, $data); } return $data; } } user_error(sprintf(Base::E_Open, $file), E_USER_ERROR); }
/** * Instantiate class * @param $onsuspect callback **/ function __construct($onsuspect = NULL) { session_set_save_handler(array($this, 'open'), array($this, 'close'), array($this, 'read'), array($this, 'write'), array($this, 'destroy'), array($this, 'cleanup')); register_shutdown_function('session_commit'); @session_start(); $fw = Base::instance(); $headers = $fw->get('HEADERS'); if (($ip = $this->ip()) && $ip != $fw->get('IP') || ($agent = $this->agent()) && (!isset($headers['User-Agent']) || $agent != $headers['User-Agent'])) { if (isset($onsuspect)) { $fw->call($onsuspect, array($this)); } else { session_destroy(); $fw->error(403); } } $csrf = $fw->hash($fw->get('ROOT') . $fw->get('BASE')) . '.' . $fw->hash(mt_rand()); $jar = $fw->get('JAR'); if (Cache::instance()->exists(($this->sid = session_id()) . '.@', $data)) { $data['csrf'] = $csrf; Cache::instance()->set($this->sid . '.@', $data, $jar['expire'] ? $jar['expire'] - time() : 0); } }
/** * Strip Javascript/CSS files of extraneous whitespaces and comments; * Return combined output as a minified string * @return string * @param $files string|array * @param $mime string * @param $header bool * @param $path string **/ function minify($files, $mime = NULL, $header = TRUE, $path = NULL) { $fw = Base::instance(); if (is_string($files)) { $files = $fw->split($files); } if (!$mime) { $mime = $this->mime($files[0]); } preg_match('/\\w+$/', $files[0], $ext); $cache = Cache::instance(); $dst = ''; if (!isset($path)) { $path = $fw->get('UI') . ';./'; } foreach ($fw->split($path, FALSE) as $dir) { foreach ($files as $file) { if (is_file($save = $fw->fixslashes($dir . $file))) { if ($fw->get('CACHE') && ($cached = $cache->exists($hash = $fw->hash($save) . '.' . $ext[0], $data)) && $cached[0] > filemtime($save)) { $dst .= $data; } else { $data = ''; $src = $fw->read($save); for ($ptr = 0, $len = strlen($src); $ptr < $len;) { if (preg_match('/^@import\\h+url' . '\\(\\h*([\'"])(.+?)\\1\\h*\\)[^;]*;/', substr($src, $ptr), $parts)) { $path = dirname($file); $data .= $this->minify(($path ? $path . '/' : '') . $parts[2], $mime, $header); $ptr += strlen($parts[0]); continue; } if ($src[$ptr] == '/') { if ($src[$ptr + 1] == '*') { // Multiline comment $str = strstr(substr($src, $ptr + 2), '*/', TRUE); $ptr += strlen($str) + 4; } elseif ($src[$ptr + 1] == '/') { // Single-line comment $str = strstr(substr($src, $ptr + 2), "\n", TRUE); $ptr += strlen($str) + 2; } else { // Presume it's a regex pattern $regex = TRUE; // Backtrack and validate for ($ofs = $ptr; $ofs; $ofs--) { // Pattern should be preceded by // open parenthesis, colon, // object property or operator if (preg_match('/(return|[(:=!+\\-*&|])$/', substr($src, 0, $ofs))) { $data .= '/'; $ptr++; while ($ptr < $len) { $data .= $src[$ptr]; $ptr++; if ($src[$ptr - 1] == '\\') { $data .= $src[$ptr]; $ptr++; } elseif ($src[$ptr - 1] == '/') { break; } } break; } elseif (!ctype_space($src[$ofs - 1])) { // Not a regex pattern $regex = FALSE; break; } } if (!$regex) { // Division operator $data .= $src[$ptr]; $ptr++; } } continue; } if (in_array($src[$ptr], array('\'', '"'))) { $match = $src[$ptr]; $data .= $match; $ptr++; // String literal while ($ptr < $len) { $data .= $src[$ptr]; $ptr++; if ($src[$ptr - 1] == '\\') { $data .= $src[$ptr]; $ptr++; } elseif ($src[$ptr - 1] == $match) { break; } } continue; } if (ctype_space($src[$ptr])) { if ($ptr + 1 < strlen($src) && preg_match('/[\\w' . ($ext[0] == 'css' ? '#\\.%+\\-*()\\[\\]' : '\\$') . ']{2}|' . '[+\\-]{2}/', substr($data, -1) . $src[$ptr + 1])) { $data .= ' '; } $ptr++; continue; } $data .= $src[$ptr]; $ptr++; } if ($fw->get('CACHE')) { $cache->set($hash, $data); } $dst .= $data; } } } } if (PHP_SAPI != 'cli' && $header) { header('Content-Type: ' . $mime . '; charset=' . $fw->get('ENCODING')); } return $dst; }
/** * Match routes against incoming URI * @return mixed **/ function run() { if ($this->blacklisted($this->hive['IP'])) { // Spammer detected $this->error(403); } if (!$this->hive['ROUTES']) { // No routes defined user_error(self::E_Routes, E_USER_ERROR); } // Match specific routes first $paths = array(); foreach ($keys = array_keys($this->hive['ROUTES']) as $key) { $paths[] = str_replace('@', '*@', $key); } $vals = array_values($this->hive['ROUTES']); array_multisort($paths, SORT_DESC, $keys, $vals); $this->hive['ROUTES'] = array_combine($keys, $vals); // Convert to BASE-relative URL $req = $this->rel($this->hive['URI']); if ($cors = isset($this->hive['HEADERS']['Origin']) && $this->hive['CORS']['origin']) { $cors = $this->hive['CORS']; header('Access-Control-Allow-Origin: ' . $cors['origin']); header('Access-Control-Allow-Credentials: ' . ($cors['credentials'] ? 'true' : 'false')); } $allowed = array(); foreach ($this->hive['ROUTES'] as $pattern => $routes) { if (!($args = $this->mask($pattern, $req))) { continue; } ksort($args); $route = NULL; if (isset($routes[$ptr = $this->hive['AJAX'] + 1][$this->hive['VERB']])) { $route = $routes[$ptr]; } elseif (isset($routes[self::REQ_SYNC | self::REQ_AJAX])) { $route = $routes[self::REQ_SYNC | self::REQ_AJAX]; } if (!$route) { continue; } if ($this->hive['VERB'] != 'OPTIONS' && isset($route[$this->hive['VERB']])) { $parts = parse_url($req); if ($this->hive['VERB'] == 'GET' && preg_match('/.+\\/$/', $parts['path'])) { $this->reroute(substr($parts['path'], 0, -1) . (isset($parts['query']) ? '?' . $parts['query'] : '')); } list($handler, $ttl, $kbps, $alias) = $route[$this->hive['VERB']]; if (is_bool(strpos($pattern, '/*'))) { foreach (array_keys($args) as $key) { if (is_numeric($key) && $key) { unset($args[$key]); } } } // Capture values of route pattern tokens $this->hive['PARAMS'] = $args = array_map('urldecode', $args); // Save matching route $this->hive['ALIAS'] = $alias; $this->hive['PATTERN'] = $pattern; if ($cors && $cors['expose']) { header('Access-Control-Expose-Headers: ' . (is_array($cors['expose']) ? implode(',', $cors['expose']) : $cors['expose'])); } if (is_string($handler)) { // Replace route pattern tokens in handler if any $handler = preg_replace_callback('/@(\\w+\\b)/', function ($id) use($args) { return isset($args[$id[1]]) ? $args[$id[1]] : $id[0]; }, $handler); if (preg_match('/(.+)\\h*(?:->|::)/', $handler, $match) && !class_exists($match[1])) { $this->error(404); } } // Process request $result = NULL; $body = ''; $now = microtime(TRUE); if (preg_match('/GET|HEAD/', $this->hive['VERB']) && $ttl) { // Only GET and HEAD requests are cacheable $headers = $this->hive['HEADERS']; $cache = Cache::instance(); $cached = $cache->exists($hash = $this->hash($this->hive['VERB'] . ' ' . $this->hive['URI']) . '.url', $data); if ($cached && $cached[0] + $ttl > $now) { if (isset($headers['If-Modified-Since']) && strtotime($headers['If-Modified-Since']) + $ttl > $now) { $this->status(304); die; } // Retrieve from cache backend list($headers, $body, $result) = $data; if (PHP_SAPI != 'cli') { array_walk($headers, 'header'); } $this->expire($cached[0] + $ttl - $now); } else { // Expire HTTP client-cached page $this->expire($ttl); } } else { $this->expire(0); } if (!strlen($body)) { if (!$this->hive['RAW'] && !$this->hive['BODY']) { $this->hive['BODY'] = file_get_contents('php://input'); } ob_start(); // Call route handler $result = $this->call($handler, array($this, $args), 'beforeroute,afterroute'); $body = ob_get_clean(); // Template Parsing if ($this->hive['TEMPLATE']) { $this->hive['RESPONSE'] = $body; $body = $this->send($this->hive['TEMPLATE'], true); } if (isset($cache) && !error_get_last()) { // Save to cache backend $cache->set($hash, array(preg_grep('/Set-Cookie\\:/', headers_list(), PREG_GREP_INVERT), $body, $result), $ttl); } } $this->hive['RESPONSE'] = $body; if (!$this->hive['QUIET']) { if ($kbps) { $ctr = 0; foreach (str_split($body, 1024) as $part) { // Throttle output $ctr++; if ($ctr / $kbps > ($elapsed = microtime(TRUE) - $now) && !connection_aborted()) { usleep(1000000.0 * ($ctr / $kbps - $elapsed)); } echo $part; } } else { echo $body; } } return $result; } $allowed = array_merge($allowed, array_keys($route)); } if (!$allowed) { // URL doesn't match any route $this->error(404); } elseif (PHP_SAPI != 'cli') { // Unhandled HTTP method header('Allow: ' . implode(',', array_unique($allowed))); if ($cors) { header('Access-Control-Allow-Methods: OPTIONS,' . implode(',', $allowed)); if ($cors['headers']) { header('Access-Control-Allow-Headers: ' . (is_array($cors['headers']) ? implode(',', $cors['headers']) : $cors['headers'])); } if ($cors['ttl'] > 0) { header('Access-Control-Max-Age: ' . $cors['ttl']); } } if ($this->hive['VERB'] != 'OPTIONS') { $this->error(405); } } return FALSE; }