. ** ** itjs.class - functions to handle javascript inclusion */ class itjs { static $charset; /** * Send HTTP headers (content-type) to transmit javascript code */ function send_headers($charset = null) { if (!$charset) $charset = ini_get('default_charset') ?: 'iso-8859-1'; self::$charset = $charset; header("Content-Type: application/x-javascript; charset=$charset"); header('Expires: ' . gmdate('D, d M Y H:i:s', time()+10) . ' GMT'); # prevent broken data on IE reloads } /** * Serialize the result into a javascript script * @param $values Array with values to be serialized * @param $envelope Encapsulate the data when callback function is provided (iframe solution) * @return String with javascript code to be sent to client */ static function serialize($values, $envelope = false) { if (($envelope || isset($values['eof'])) && ($callback = it::replace(array('[^\w.]' => ""), $_REQUEST['itjs_call']))) { $target = $_REQUEST['itjs_iframe'] ? "parent" : "window"; list($itclass) = explode('.', $callback); $header = "$target.$itclass && $target.$callback && $target.$callback.dataReady("; $footer = "," . intval($_REQUEST['itjs_callid']) . ");"; if ($_REQUEST['itjs_iframe']) # iframe-based loading required by Opera 7 { $header = '"; } } $result = $header . itjs::encode($values) . $footer; return it_untaint($result); } /* * Encode the result into a javascript array to transfer and eval() in client */ static function encode($values) { if (!is_array($values)) return json_encode($values, JSON_UNESCAPED_UNICODE); static $jskeyword = array("abstract" => 1, "boolean" => 1, "break" => 1, "byte" => 1, "case" => 1, "catch" => 1, "char" => 1, "class" => 1, "const" => 1, "continue" => 1, "debugger" => 1, "default" => 1, "delete" => 1, "do" => 1, "double" => 1, "each" => 1, "else" => 1, "enum" => 1, "export" => 1, "extends" => 1, "false" => 1, "final" => 1, "finally" => 1, "float" => 1, "for" => 1, "function" => 1, "goto" => 1, "if" => 1, "implements" => 1, "import" => 1, "in" => 1, "instanceof" => 1, "int" => 1, "interface" => 1, "long" => 1, "namespace" => 1, "native" => 1, "new" => 1, "null" => 1, "package" => 1, "private" => 1, "protected" => 1, "public" => 1, "return" => 1, "short" => 1, "static" => 1, "super" => 1, "switch" => 1, "synchronized" => 1, "this" => 1, "throw" => 1, "throws" => 1, "transient" => 1, "true" => 1, "try" => 1, "typeof" => 1, "var" => 1, "void" => 1, "volatile" => 1, "while" => 1, "with" => 1, "xml" => 1); $charset = self::$charset ?: ini_get('default_charset'); $texts = ($values === array_values($values)) ? "[]0 " : "{}1\n"; # Numerical or associative array $result = $texts[0]; foreach ($values as $key => $value) { $result .= $separator; if ($texts[2]) { if ($jskeyword[$key] || !preg_match('/^[a-z_]\w*$/i', $key)) $key = "'" . addslashes($key) . "'"; $result .= "$key:"; } if ($value === true) $result .= 'true'; else if ($value === false) $result .= 'false'; else if (!is_array($value)) { $quote = (strval(intval($value)) === strval($value)) ? "" : '"'; if (strtolower($charset) != "utf-8") $value = strtr($value, array("\x84" => '"', "\x93" => '"', "\x94" => '"')); $string = strtr($value, array("\0" => '\\0', '"' => '\\"', ""<\\/", "\n" => '\\n', "\r" => '\\r', "\t" => '\\t', "\\" => '\\\\')); $string = $GLOBALS['itjs_defaultconfig']['latin2unicode'] ? preg_replace_callback('/([\xa0-\xff])/', function($m) { return sprintf("\\u%04x", ord($m[1])); }, $string) : $string; $result .= $quote . $string . $quote; } else $result .= itjs::encode($value); $separator = "," . $texts[3]; } $result .= $texts[1]; return $result; } /** * Convert UNTRUSTED comma separated filelist string to trusted local filenames. Missing files are ignored. */ static function filenames($filelist) { $result = array(); $local = $GLOBALS['ULTRAHOME'] . "/itjs"; $itjs = "/www/server/phpinclude/itools/itjs"; $libsearch = strlen($GLOBALS['debug_lib']) > 1 ? "/www/lib-" . $GLOBALS['debug_lib'] . ".search.ch" : "/www/lib.search.ch"; $special = array( "itools" => "$itjs/it.js,$itjs/http.js,$itjs/loader.js,$itjs/state.js,$itjs/timer.js", "boot.js" => "$itjs/boot.js", "state.html" => "$itjs/state.html", "error.gif" => "$itjs/error.gif", "search.css" => "$libsearch/doc/search.css", "prettyprint.css" => "$libsearch/doc/prettyprint.css", ); foreach (it::match("[-\w.=?&]+", basename($filelist), array('all' => true)) as $file) # split by comma but ignore illegal chars { $filenames = $special[$file] ?: (file_exists("$local/" . it::match('^[^?]*', $file)) ? "$local/$file" : "$libsearch/itjs/$file"); foreach (explode(",", $filenames) as $filename) if (!$seen[$filename]++ && file_exists(it::match('^[^?]*', $filename))) $result[] = $filename; } return $result; } /** * Return content of (php-interpreted by default) TRUSTED filenames that will be sent to client. Files must exist. */ static function filecontents($filenames) { foreach ($filenames as $filename) { ob_start(); # Needs to capture inside loop to guarantee file order if (!($_GET['w3c'] && it::match('jquery-ui.css|owl.carousel.css|mobiscroll.custom|hopscotch.css|leaflet.css', $filename))) # BLACKLIST { $origget = $_GET; list($filename, $paramstr) = explode("?", $filename); if ($paramstr) parse_str($paramstr, $_GET); $result .= it::replace(array('^1$' => ""), it::match('\.(js|css|htc)$', $filename) ? include_once($filename) : (file_exists($filename) ? it::file_get_contents($filename) : it_url::get($filename)), array('utf8' => false)); $_GET = $origget; } $result .= ob_get_clean(); } return $result; } /** * Strip comments and trim lines * @param $code String containing javascript code to be stripped * @return Naked code */ static function strip($code) { if (!it::is_devel()) $code = preg_replace(array( '|\s//.*$|m', '|\s+/\*.*?\*/|s', # MUST require at least one space before /* (jquery.js) but MUST NOT require space before */ (searchmap.js) '|^\s+|m' ), array(), $code); if (EDC('print')) $code = it::replace(array('@media\s+print' => '@media screen, print'), $code); return $code; } /** * Compute checksum for list of files * @param $fnlist Either comma separated UNTRUSTED url (will check itjs/ and lib.search.ch/itjs/) or array of TRUSTED filenames * @return Checksum for given files */ static function checksum($fnlist, $p = array()) { $p += array('short_expire' => true); $filenames = array(); foreach (is_array($fnlist) ? $fnlist : itjs::filenames($fnlist) as $filename) $filenames[] = !file_exists($filename) && file_exists($t = it::replace(array('^/www/[^/]*' => "/www/lib.search.ch"), $filename)) ? $t : $filename; $key = "itjs_" . md5(join("", it::map('"$v" . @filemtime("$v")', $filenames))); if ($filenames && $p['short_expire'] && (time() - max(@array_map('filemtime', $filenames)) < 60)) return "-"; # trigger short expire, our file may not yet be up to date on other slaves else if ($filenames) return it_cache::get($key) ?: it_cache::put($key, substr(md5("X" . self::filecontents($filenames)), 0, 10), array('ttl' => 60)); } /** * Convert url or TRUSTED local path to url that triggers far future expire by appending c=checksum */ function crcurl($url, $p = array()) { if (it::match('^(http|//)', $url)) # remote url, must fetch to crc list($fn, $short_expire) = array(it_url::get_cache(array('url' => $url, 'maxage' => 3600, 'id' => "itjs_crcurl") + $p), false); else list($fn, $short_expire) = array(($m = it::match("^//(\w+)(/.*)", $url)) ? "/www/$m[0].search.ch" . $m[1] : $GLOBALS['ULTRAHOME'] . $url, true);; return it::match('#', $url) ? U(trim($url, "#")) : U($url, array('c' => self::checksum(array($fn), array('short_expire' => $short_expire)))); } # $p['nocrc'] means no $_REQUEST['c'] is needed for far future expire static function far_future_headers($p = array()) { $crc = $_REQUEST['c'] ?: $_REQUEST['s']; if ($crc != "-" && !$_SERVER['HTTP_CACHE_CONTROL'] && $_SERVER['HTTP_IF_NONE_MATCH'] && !it::is_devel() && !$_REQUEST['retry']) { header("HTTP/1.0 304 Not Modified"); # client should always keep the component that fits the page it has exit; } if ($crc != "-") @header("Etag: alwaysvalid"); # we have checksums in the url. client should always keep the version he downloaded along with the html if (it::is_live() && !$_REQUEST['retry']) { $keeptime = $crc == "-" ? 0 : ($crc || $p['nocrc'] ? 30*86400 : 900); # long expire if checksum present header("Cache-Control: max-age=$keeptime, private"); # proxies should not cache since contents of same url can differ between browsers header("Expires: " . gmdate("D, d M Y H:i:s", time() + $keeptime). " GMT"); } } } ?>