Files
selima-perl/lib/php/monica/init.inc.php
2026-03-10 21:31:43 +08:00

668 lines
23 KiB
PHP

<?php
// File name: init.inc.php
// Description: PHP subroutines to initial the environment
// Date: 2002-04-12
// Author: imacat <imacat@pristine.com.tw>
// Copyright: Copyright (C) 2002-2011 Pristine Communications
// Set the include path
if (!defined("INCPATH_SET")) {
require_once dirname(__FILE__) . "/incpath.inc.php";
}
// Referenced subroutines
require_once "monica/altlang.inc.php";
require_once "monica/cgiemu.inc.php";
require_once "monica/chkpriv.inc.php";
require_once "monica/curtime.inc.php";
require_once "monica/decform.inc.php";
require_once "monica/errhndl.inc.php";
require_once "monica/formfunc.inc.php";
require_once "monica/getlang.inc.php";
require_once "monica/gettext.inc.php";
require_once "monica/hires.inc.php";
require_once "monica/http.inc.php";
require_once "monica/https.inc.php";
require_once "monica/lastmodf.inc.php";
require_once "monica/listpref.inc.php";
require_once "monica/lninfo.inc.php";
require_once "monica/login.inc.php";
require_once "monica/logout.inc.php";
require_once "monica/page2rel.inc.php";
require_once "monica/pagefunc.inc.php";
require_once "monica/rel2abs.inc.php";
require_once "monica/requri.inc.php";
require_once "monica/scptpriv.inc.php";
require_once "monica/sql.inc.php";
require_once "monica/sqlconst.inc.php";
require_once "monica/unauth.inc.php";
require_once "monica/unicode.inc.php";
require_once "monica/upload.inc.php";
require_once "monica/xhtml.inc.php";
require_once "monica/xfileio.inc.php";
// Settings
if (!defined("FIELD_HARD_LIMIT")) {
define("FIELD_HARD_LIMIT", 307200);
}
if (!defined("FORM_HARD_LIMIT")) {
define("FORM_HARD_LIMIT", 512000);
}
// initenv: Initialize the script environment
// Input:
// $args: An associative array of arguments, where
// $args["allowed"]: The allowed request methods
// $args["restricted"]: Whether restricted to those permitted here
// $args["session"]: Whether to start a session (boolean)
// $args["sql"]: Which SQL (SQL_NONE, SQL_POSTGRESQL, SQL_MYSQL)
// $args["sql_lock"]: The SQL tables to lock in advance
function initenv($args)
{
// The submitted data
$FORM =& get_or_post();
// Parse the arguments
$session = true;
if (array_key_exists("session", $args)) {
$session = $args["session"];
} elseif (defined("START_SESSION")) {
$session = START_SESSION;
}
settype($session, "boolean");
$sql = SQL_POSTGRESQL;
if ( array_key_exists("sql", $args)
&& in_array($args["sql"], $GLOBALS["VALID_SQLS"])) {
$sql = $args["sql"];
} elseif (defined("START_SQL")
&& in_array(START_SQL, $GLOBALS["VALID_SQLS"])) {
$sql = START_SQL;
}
$GLOBALS["SQL_DBTYPE"] = $sql;
$restricted = false;
if (array_key_exists("restricted", $args)) {
$restricted = $args["restricted"];
}
$lastmod = false;
if (array_key_exists("lastmod", $args)) {
$lastmod = $args["lastmod"];
}
settype($lastmod, "boolean");
$logtime = false;
if (array_key_exists("logtime", $args)) {
$logtime = $args["logtime"];
}
settype($logtime, "boolean");
define("LOGTIME", $logtime);
// Set the custom error handler: http_500(): Standard error handler for Monica
set_error_handler("http500_error_handler");
// Secure the script environment
secure_env();
// Set the multi-byte environment
mb_internal_encoding("UTF-8");
// Initial the gettext locale
_init_initgettext();
// Check the last output buffering level
define("LAST_OB_LEVEL", (ini_get("output_buffering") == "" || !IS_CGI)? 0: 1);
// Start output buffering (till one level more than LAST_OB_LEVEL)
ob_start();
// Block FunWebProduct
// See http://www.networkworld.com/newsletters/web/2003/1208web2.html
if ( array_key_exists("HTTP_USER_AGENT", $_SERVER)
&& strpos($_SERVER["HTTP_USER_AGENT"], "FunWebProduct") !== false) {
http_403(N_("Sorry, browsers with FunWebProduct plugin (Smiley, PopSwatter, Spin4Dough, My Mail Signature, My Mail Stationery, My Mail Stamp, Cursor Mania, etc.) are are not welcome. It duplicates your request and produces high load and even crashes to our server. Please remove it first before you visit us."));
}
// Block bad-behaved e-mail crawlers
// Some bad-behaved e-mail crawlers cannot deal with the parent
// directory ".." and ampersands, and attach them to the URI infinitely
if (strstr(REQUEST_PATH, "/..") !== false || strstr(REQUEST_URI, "&amp;") !== false) {
http_400(false);
}
// Limit the request methods, default to GET, HEAD and POST
// Set to null for no check. Useful for testing and debugging
$allowed = array_key_exists("allowed", $args)?
$args["allowed"]: array("GET", "HEAD", "POST");
if (!is_null($allowed) && !in_array($_SERVER["REQUEST_METHOD"], $allowed)) {
http_405($allowed);
}
// Block bad robots
_init_block_bad_robots();
// Initialize the session
if ($session) {
// Form processed with HTTPs
$https = false;
if (array_key_exists("https", $FORM)) {
$https = $FORM["https"]? true: false;
}
// We are HTTPS, processing a form that came from
// another non-HTTPS virtual host.
// Overriding the session ID. to keep the original session.
if ($https && is_https()) {
// Use the provided session ID
if (array_key_exists(session_name(), $FORM)) {
session_id($FORM[session_name()]);
}
}
// Avoid bad robots producing bad session ID.
$GLOBALS["php_errormsg"] = null;
set_error_handler("null_error_handler");
session_start();
if (!is_null($GLOBALS["php_errormsg"])) {
http_400($GLOBALS["php_errormsg"]);
}
restore_error_handler();
}
// Prevent huge sized request
check_request_size();
// If client has not logged in on restricted area, we can
// bypass SQL connection to save our work
if (IS_CGI && is_null(get_login_sn()) && $restricted !== false) {
unauth();
}
// Initialize the SQL connection
if ($sql) {
if (defined("NODATABASE") && NODATABASE) {
http_503("Database shut down for maintainance.");
}
sql_connect();
}
// Only available on systems with membership turned on
if ($sql && use_users() && $session) {
// Update the log-in infomation
upd_login_info();
// Check the client permission
if ($restricted !== false && !is_script_permitted($restricted)) {
unauth();
}
// Check if this site is closed for policy
_init_check_nologin();
}
// Prepare the SQL tables to lock
if ($sql && array_key_exists("sql_lock", $args)) {
// Read-only on non-POSTed forms
if ($_SERVER["REQUEST_METHOD"] != "POST") {
foreach (array_keys($args["sql_lock"]) as $table) {
$args["sql_lock"][$table] = LOCK_SH;
}
// Add mtime to POSTed forms
} else {
if (in_array("mtime", sql_tables())) {
$args["sql_lock"]["mtime"] = LOCK_EX;
}
}
// Supply the default locks
if (use_users()) {
$default_locks = array("users", "groups", "scptpriv", "userpref",
"users AS createdby", "users AS updatedby");
foreach ($default_locks as $table) {
if (!array_key_exists($table, $args["sql_lock"])) {
$args["sql_lock"][$table] = LOCK_SH;
}
}
}
}
// Check the last modified
if ($lastmod) {
// Set the database tables to check
$tables = array();
if (array_key_exists("lmtables", $args)) {
$tables = array_merge($tables, $args["lmtables"]);
}
// Add the locked tables automatically
if (array_key_exists("sql_lock", $args)) {
$tables = array_merge($tables, array_keys($args["sql_lock"]));
}
// Set the files to check
$files = array();
if (array_key_exists("lmfiles", $args)) {
$files = array_merge($files, $args["lmfiles"]);
}
$callers = debug_backtrace();
if (not_modified($tables, $files,
basename($callers[count($callers)-1]["file"]))) {
http_304();
}
}
// Lock the SQL tables
if ($sql && array_key_exists("sql_lock", $args)) {
sql_lock($args["sql_lock"]);
}
// Fetch the current data
if (function_exists("fetch_current")) {
fetch_current();
}
// Decode user input FORMs to UTF-8
decode_forms();
// Process the list preference form
if (form_type() == "listpref") {
if ( array_key_exists("domain", $_POST)
&& class_exists($_POST["domain"])) {
$LIST = new $_POST["domain"];
$LIST->set_listpref();
} else {
$handler = new ListPreference($_POST);
$handler->main();
}
}
return;
}
// restenv: Restore the script environment
function restenv()
{
// Disconnect SQL
if ($GLOBALS["SQL_DBTYPE"] != SQL_NONE) {
sql_close();
}
// Print the page and end the output buffering, if it exists
if (ob_get_level() > LAST_OB_LEVEL) {
// Return from the innermost level to level 1
while (ob_get_level() > LAST_OB_LEVEL + 1) {
ob_end_flush();
}
// Obtain the final content
$html = ob_get_contents();
ob_end_clean();
// No content -- HTTP 204
if ($html == "") {
http_204();
}
// The content type
$type = array_key_exists("CONTENT_TYPE", $GLOBALS)?
$GLOBALS["CONTENT_TYPE"]: xhtml_content_type();
$is_html = preg_match("/^(?:text\/html|application\/xhtml\+xml)\b/", $type)? true: false;
$is_rss = $type == "application/rss+xml";
// application/rss+xml is not supported yet
if ($is_rss) {
$type = "application/xml";
}
$is_text = preg_match("/^(?:text\/plain)\b/", $type)? true: false;
$is_csv = preg_match("/^(?:text\/csv)\b/", $type)? true: false;
$is_js = preg_match("/^(?:text\/javascript)\b/", $type)? true: false;
$is_attach = array_key_exists("CONTENT_DISPOSITION", $GLOBALS) &&
preg_match("/^attachment\b/", $GLOBALS["CONTENT_DISPOSITION"]);
// Do the run-time replacements
if (($is_html || $is_rss || $is_text || $is_js) && function_exists("page_replacements")) {
$rep = page_replacements();
foreach (array_keys($rep) as $key) {
$html = str_replace("<!--monica:$key-->", $rep[$key]["content"], $html);
}
}
// Fix the HTML output
if ($is_html) {
if (preg_match("/; charset=([^ ;]+)/", $type, $m)) {
$charset = $m[1];
} else {
$charset = getlang(LN_CHARSET);
$type .= "; charset=$charset";
}
// Convert the URLs to relative
if ($is_attach) {
$html = page2abs($html, REQUEST_PATH);
} else {
$html = page2rel($html, REQUEST_PATH);
}
// Mung the e-mail at-signs (@)
$html = str_replace("@", "&#64;", $html);
// Decode the e-mail at-signs (@) of spamtrap
$html = str_replace("spamtrap&#64;", "spamtrap@", $html);
// Convert to the desired character set
$html = page_encode($html, $charset);
// Fix the RSS output
} elseif ($is_rss) {
if (preg_match("/; charset=([^ ;]+)/", $type, $m)) {
$charset = $m[1];
} else {
$charset = getlang(LN_CHARSET);
$type .= "; charset=$charset";
}
// Mung the e-mail at-signs (@)
$html = str_replace("@", "&#64;", $html);
// Decode the e-mail at-signs (@) of spamtrap
$html = str_replace("spamtrap&#64;", "spamtrap@", $html);
// Convert to the desired character set
$html = page_encode($html, $charset);
// Fix the plain text output
} elseif ($is_text) {
if (preg_match("/; charset=([^ ;]+)/", $type, $m)) {
$charset = $m[1];
} else {
$charset = "UTF-8";
$type .= "; charset=$charset";
}
// Mung the e-mail at-signs (@)
$html = str_replace("@", "-at-", $html);
// Decode the e-mail at-signs (@) of spamtrap
$html = str_replace("spamtrap-at-", "spamtrap@", $html);
// Convert to the desired character set
$html = h_encode($html, $charset);
// Fix the comma-seperated values output
} elseif ($is_csv) {
if (preg_match("/; charset=([^ ;]+)/", $type, $m)) {
$charset = $m[1];
} else {
$charset = "UTF-8";
$type .= "; charset=$charset";
}
// Convert to the desired character set
$html = h_encode($html, $charset);
// Fix the javascript output
} elseif ($is_js) {
if (preg_match("/; charset=([^ ;]+)/", $type, $m)) {
$charset = $m[1];
} else {
$charset = getlang(LN_CHARSET);
$type .= "; charset=$charset";
}
// Convert to the desired character set
$html = h_encode($html, $charset);
}
header("Content-Type: $type");
header("Content-Length: " . strlen($html));
if (array_key_exists("CONTENT_DISPOSITION", $GLOBALS)) {
header("Content-Disposition: " . $GLOBALS["CONTENT_DISPOSITION"]);
}
header("Accept-Ranges: none");
if ( substr($type, 0, 5) == "text/"
|| $is_html
|| substr($type, 0, 15) == "application/pdf") {
header("Content-Language: " . getlang(LN_NAME));
}
// Client cache
if (array_key_exists("LAST_MODIFIED", $GLOBALS)) {
if (!is_null(get_login_sn())) {
header("Cache-Control: private");
} else {
header("Cache-Control: public");
}
header("Last-Modified: " . date("r", $GLOBALS["LAST_MODIFIED"]));
} else {
header("Cache-Control: no-cache");
}
// Content negotiation, see HTTP/1.1 section 13.6
if ( count($GLOBALS["ALL_LINGUAS"]) > 1
&& $_SERVER["REQUEST_METHOD"] != "POST"
&& $_SERVER["REQUEST_METHOD"] != "PUT"
&& !array_key_exists("lang", $_GET)) {
header("Content-Location: " . rel2abs(altlang(getlang(), page_param())));
header("Vary: negotiate,accept,accept-language,cookie");
}
// Print the page body
if ($_SERVER["REQUEST_METHOD"] != "HEAD") {
echo $html;
}
}
// Purge the file cache
if (isset($_SESSION) && array_key_exists("savefile", $_SESSION)) {
$FILES =& file_deposit();
$total = 0;
foreach (array_keys($FILES) as $sn) {
// Not a normal record
if ( !is_array($FILES[$sn])
|| !array_key_exists("size", $FILES[$sn])) {
continue;
}
$total += $FILES[$sn]["size"];
// Too larged
if ($total > UPLOAD_DEPOSIT_MAX) {
clear_file_deposit();
break;
}
}
}
// Print the processing time for debugging purpose
if (defined("LOGTIME") && LOGTIME) {
error_log("Process time: " . (time_hires() - T_START) . " sec.");
}
return;
}
// secure_env: Secure the script environment
function secure_env()
{
$ENVLIST = array("PATH", "USER", "MAIL", "HOME", "USERNAME",
"LOGNAME", "PWD", "ENV", "KDEDIR");
foreach ($ENVLIST as $env) {
if (getenv($env) !== false) {
putenv("$env=");
}
}
return;
}
// check_request_size: Prevent huge sized request
function check_request_size()
{
$len = _init_check_array_size($_GET);
if ($len === false) http_413();
if ($len > FORM_HARD_LIMIT) http_413();
$len = _init_check_array_size($_POST);
if ($len === false) http_413();
if ($len > FORM_HARD_LIMIT) http_413();
$len = _init_check_array_size($_COOKIE);
if ($len === false) http_413();
if ($len > FORM_HARD_LIMIT) http_413();
$len = _init_check_array_size($_SERVER);
if ($len === false) http_413();
if ($len > FORM_HARD_LIMIT) http_413();
return;
}
// _init_check_array_size: Recursively check the size of an array
function _init_check_array_size(&$a)
{
// Check the array size
if (gettype($a) != "array") {
$len = strlen($a);
if ($len > FIELD_HARD_LIMIT) {
return false;
}
return $len;
}
// Check the array size
$len = 0;
foreach (array_keys($a) as $k) {
$len += strlen($k);
if (gettype($a[$k]) == "array") {
$len0 = _init_check_array_size($a[$k]);
if ($len0 === false) {
return false;
}
$len += $len0;
} else {
$len0 = strlen($a[$k]);
if ($len0 > FIELD_HARD_LIMIT) {
return false;
}
$len += $len0;
}
}
return $len;
}
// _init_check_nologin: If this site is closed for policy
function _init_check_nologin()
{
// Not logged in - no checks needed
if (is_null(get_login_sn())) {
return;
}
// NOLOGIN: Only super-users can log in now
if (defined("NOLOGIN") && NOLOGIN) {
// Only super-users can log into no-log-in websites
if (is_su()) {
return;
}
logout();
// Deny access to the development site
ob_end_clean();
ob_start();
html_header(_("Log-In Closed"));
html_title(_("Log-In Closed"));
html_message(_("Log-in is temporarily closed for maintainance now. Please come again later. Sorry for the inconvienence."));
html_footer();
$html = ob_get_contents();
ob_end_clean();
if ($_SERVER["REQUEST_METHOD"] != "HEAD") {
echo $html;
}
// No need to return
exit;
// CLOSEDEV: Only developers can log in now
} elseif (defined("CLOSEDEV") && CLOSEDEV) {
if (is_group("dev")) {
return;
}
logout();
// Deny access to the development site
ob_end_clean();
ob_start();
html_header(_("Development Site Closed"));
html_title(_("Development Site Closed"));
html_message(_("Development site is closed. Please work on the live site."));
html_footer();
$html = ob_get_contents();
ob_end_clean();
if ($_SERVER["REQUEST_METHOD"] != "HEAD") {
echo $html;
}
// No need to return
exit;
}
}
// _init_block_bad_robots: Block bad robots
function _init_block_bad_robots()
{
// User-Agent: was sent
if (array_key_exists("HTTP_USER_AGENT", $_SERVER)) {
// Check MSIE 6 and unbalanced brackets
if ($_SERVER["HTTP_USER_AGENT"] == "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1") {
http_403();
}
// Check User-Agent in user-agent identification
if (substr($_SERVER["HTTP_USER_AGENT"], 0, 11) == "User-Agent:") {
http_403();
}
// Check User-Agent in user-agent identification
if (strpos($_SERVER["HTTP_USER_AGENT"], "DTS Agent") !== false) {
http_403(false);
}
// MSIE 6 must be HTTP/1.1
// -- disabled 2006-07-07. Lots of innocent set this way (why?)
// Opera pretends to be MSIE 6, and uses HTTP/1.0 (why?)
//if ( preg_match("/\bMSIE [456]/", $_SERVER["HTTP_USER_AGENT"])
// && strstr($_SERVER["HTTP_USER_AGENT"], "Opera") === false
// && $_SERVER["SERVER_PROTOCOL"] != "HTTP/1.1") {
// http_403();
//}
}
// Check huge amount of requests from a same host in a short time
if (IS_CGI) {
$file = ini_get("session.save_path") . "/ipacct.txt";
$count = 0;
$newrecs = array();
$lastseen = null;
// Check the current records
if (file_exists($file)) {
$fp = fopen($file, "r+");
flock($fp, LOCK_EX);
$size = filesize($file);
// fread() does not allow reading of 0 bytes now
if ($size == 0) {
$content = "";
} else {
$content = fread($fp, $size);
}
$currecs = explode("\n", $content);
foreach ($currecs as $record) {
// Drop invalid
if (!preg_match("/^(\d+) (\d+\.\d+\.\d+\.\d+) /", $record, $m)) {
continue;
}
// We only keep 30 minutes
if (NOW - $m[1] >= 1800) {
continue;
}
$newrecs[] = "$record\n";
if ($m[2] == $_SERVER["REMOTE_ADDR"]) {
if (is_null($lastseen) || $lastseen > $m[1]) {
$lastseen = $m[1];
}
$count++;
}
}
fseek($fp, 0, SEEK_SET);
ftruncate($fp, 0);
// No current records yet
} else {
$fp = fopen($file, "w");
flock($fp, LOCK_EX);
}
// Add this record
if (is_null($lastseen)) {
$lastseen = NOW;
}
$newrecs[] = sprintf("%d %s %s\n", NOW, $_SERVER["REMOTE_ADDR"],
date("Y-m-d H:i:s", NOW));
$count++;
// Update the accounting
fwrite($fp, implode("", $newrecs));
flock($fp, LOCK_UN);
fclose($fp);
/* Debug
if (array_key_exists("debug_reqid", $_GET)) {
error_log(sprintf("req=%s NOW=%d lastseen=%d count=%d avg=%.2f block=%s",
$_GET["debug_reqid"], NOW, $lastseen, $count,
$count / ((NOW == $lastseen)? 1: NOW - $lastseen),
($count / ((NOW == $lastseen)? 1: NOW - $lastseen) > 2? "yes": "no")));
} */
// Averagely 2 script requests in 1 second from a same host - that is too much.
if ($count / ((NOW == $lastseen)? 1: NOW - $lastseen) > 2) {
http_503("Request too fast.");
}
}
}
// _init_initgettext: Initialize the gettext locale
function _init_initgettext()
{
putenv("LANG=" . getlang(LN_LOCALE));
putenv("LANGUAGE=" . getlang(LN_LOCALE));
putenv("LC_ALL=" . getlang(LN_LOCALE));
setlocale(LC_ALL, "");
bindtextdomain(COMMONDOMAIN, COMMONLOCALEDIR);
if (defined("PACKAGE")) {
bindtextdomain(PACKAGE, LOCALEDIR);
textdomain(PACKAGE);
}
}
?>