/** * remember a string during software debug * * This script appends information to temporary/debug.txt * * @param mixed something to be printed * @param string an optional label string * @return void */ public static function debug($value = '', $label = NULL) { global $context; // ensure we have a string --preserve native string $value = Logger::to_string($value, FALSE); // stamp the line $line = gmdate('Y-m-d H:i:s') . "\t"; if (isset($label)) { $line .= $label . ' '; } $line .= $value; $line .= "\n"; // ensure enough execution time Safe::set_time_limit(30); // apend to the debug file if ($handle = Safe::fopen($context['path_to_root'] . 'temporary/debug.txt', 'a')) { fwrite($handle, $line); fclose($handle); } else { echo $line; } }
/** * dynamically generate the page * * @see skins/index.php */ function send_body() { global $context; // only associates can proceed if (!Surfer::is_associate()) { Safe::header('Status: 401 Unauthorized', TRUE, 401); echo '<p>' . i18n::s('You are not allowed to perform this operation.') . "</p>\n"; // forward to the index page $menu = array('scripts/' => i18n::s('Server software')); echo Skin::build_list($menu, 'menu_bar'); // ask for confirmation } elseif (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] == 'GET') { // the splash message echo '<p>' . i18n::s('This tool will include most of the running reference PHP scripts. Any syntax error should be spotted easily.') . '</p>'; // the submit button echo '<form method="post" action="' . $context['script_url'] . '" id="main_form"><p>' . Skin::build_submit_button(i18n::s('Yes, I want to validate scripts'), NULL, NULL, 'confirmed') . '</p></form>'; // set the focus on the button Page::insert_script('$("#confirmed").focus();'); // this may take some time echo '<p>' . i18n::s('When you will click on the button the server will be immediately requested to proceed. However, because of the so many things to do on the back-end, you may have to wait for minutes before getting a response displayed. Thank you for your patience.') . '</p>'; // just do it } else { // the splash message echo '<p>' . i18n::s('All reference scripts are included, to show evidence of possible syntax errors.') . "</p>\n"; // list running scripts echo '<p>' . i18n::s('Listing files...') . BR . "\n"; // locate script files starting at root $scripts = Scripts::list_scripts_at(NULL); if (is_array($scripts) && count($scripts)) { echo BR . sprintf(i18n::s('%d scripts have been found.'), count($scripts)) . "\n"; natsort($scripts); } echo "</p>\n"; // including scripts echo '<p>' . i18n::s('Including reference scripts...') . BR . "\n"; // strip as much output as possible $_SERVER['REQUEST_METHOD'] = 'HEAD'; // we will finalize this page later on global $finalizing_fuse; $finalizing_fuse = FALSE; // take care of dependancies include_once '../behaviors/behavior.php'; include_once '../services/codec.php'; include_once '../users/authenticator.php'; // analyse each script $included_files = 0; $links_to_be_checked_manually = array(); foreach ($scripts as $file) { // ensure we have enough time to process this script Safe::set_time_limit(30); // skip run once scripts if (strpos($file, 'run_once/')) { continue; } // don't include ourself if ($file == 'scripts/validate.php') { continue; } // process only reference scripts if (!Scripts::hash($file)) { continue; } // check file content if (!($handle = Safe::fopen($file, 'rb'))) { echo sprintf(i18n::s('%s has no readable content.'), $file) . BR . "\n"; continue; } // look at the beginning of the file if (!($header = fread($handle, 16384))) { echo sprintf(i18n::s('%s has no readable content.'), $file) . BR . "\n"; fclose($handle); continue; } fclose($handle); // skip scripts that generate content asynchronously if (stripos($header, 'send_body') || stripos($header, 'page::content')) { $links_to_be_checked_manually[$file] = '(asynchronous)'; continue; } // skip scripts that would redefine our skin if (stripos($header, 'extends skin_skeleton')) { $links_to_be_checked_manually[$file] = '(skin)'; continue; } // log script inclusion on development host if ($context['with_debug'] == 'Y') { logger::remember('scripts/validate.php: inclusion of ' . $file, '', 'debug'); } // include the script and display any error $included_files += 1; $validate_stamp = time(); echo sprintf(i18n::s('inclusion of %s'), $file) . "\n"; Safe::chdir($context['path_to_root'] . dirname($file)); include_once $context['path_to_root'] . $file; $duration = time() - $validate_stamp; if ($duration) { echo ' (' . $duration . 's.)'; } echo BR; } // memory status $used_memory = ''; if (is_callable('memory_get_usage')) { $used_memory = ' (' . memory_get_usage() . ' bytes)'; } // report of included files if ($included_files > 1) { echo '<p>' . sprintf(i18n::s('%d files have been included.'), $included_files) . $used_memory . '</p>'; } // list files to be checked manually if (count($links_to_be_checked_manually)) { echo '<p>' . i18n::s('Following scripts have to be included separately:') . BR . "\n"; ksort($links_to_be_checked_manually); foreach ($links_to_be_checked_manually as $file => $label) { echo Skin::build_link($file, $file, 'basic') . ' ' . $label . BR . "\n"; } echo sprintf(i18n::s('%d files to be checked manually.'), count($links_to_be_checked_manually)) . '</p>' . "\n"; } // display the execution time $time = round(get_micro_time() - $context['start_time'], 2); echo '<p>' . sprintf(i18n::s('Script terminated in %.2f seconds.'), $time) . '</p>'; // forward to the referential building echo '<form method="get" action="' . $context['url_to_root'] . 'scripts/build.php"><p>' . "\n" . Skin::build_submit_button(i18n::s('If no error has popped up, build the reference set >>')) . "\n" . '</p></form>' . "\n"; // this may take some time echo '<p>' . i18n::s('When you will click on the button the server will be immediately requested to proceed. However, because of the so many things to do on the back-end, you may have to wait for minutes before getting a response displayed. Thank you for your patience.') . '</p>'; // clear text some scripts could have added $context['debug'] = ''; $context['extra'] = ''; $context['navigation'] = ''; $context['suffix'] = ''; $context['text'] = ''; $context['page_details'] = ''; $context['page_footer'] = ''; $context['page_menu'] = array(); $context['page_tags'] = ''; $context['page_tools'] = ''; // now we will finalize this page global $finalizing_fuse; unset($finalizing_fuse); } }
function include_hook($path) { global $context, $hooks; // animate user screen and take care of time global $scanned_directories; $scanned_directories++; // ensure enough execution time Safe::set_time_limit(30); // open the directory if (!($dir = Safe::opendir($path))) { $context['text'] .= sprintf(i18n::s('Impossible to read %s.'), $path) . BR . "\n"; return; } // browse the directory while (($item = Safe::readdir($dir)) !== FALSE) { // skip some files if ($item[0] == '.') { continue; } // load any 'hook.php', or any file which names ends with 'hook.php' $actual_item = str_replace('//', '/', $path . '/' . $item); if (preg_match('/hook\\.php$/i', $item)) { include_once $actual_item; $context['text'] .= sprintf(i18n::s('Hook %s has been included'), $actual_item) . BR . "\n"; // scan any sub dir except at server root } elseif (is_dir($actual_item) && $path != $context['path_to_root'] && !strpos($path, '/files/') && !strpos($path, '/images/')) { include_hook($actual_item); } } // close the directory Safe::closedir($dir); }
/** * process all messages from one mailbox * * This is original code compliant to RFC 1939 for the authentication, * fetching and processing of messages queued in a POP3 mailbox. * * @param array of mailbox attributes ($server, $account, $password) * @return the number of processed messages */ public static function process_queue($queue) { global $context; // useless if we don't have a valid database connection if (!$context['connection']) { return 0; } // make queue parameters available $context['mail_queue'] = $queue; // use queue parameters to connect to the server list($server, $account, $password, $allowed, $match, $section, $options, $hooks, $prefix, $suffix) = $queue; // no host, assume it's us if (!$server) { $server = $context['host_name']; } // assume the standard pop3 socket $port = 110; // use alternate port if required to do so if (preg_match('/^(.+):([0-9]+)$/', $server, $matches)) { $server = $matches[1]; $port = intval($matches[2]); } // ensure that we can support tls communications if (isset($server) && !strncmp($server, 'ssl://', 6) && is_callable('extension_loaded') && !extension_loaded('openssl')) { Logger::remember('agents/messages.php: Load the OpenSSL extension to support secured transmissions to mail server ' . $server); return 0; } // open a network connection if (!($handle = Safe::fsockopen($server, $port, $errno, $errstr, 10))) { Logger::remember('agents/messages.php: ' . sprintf('Impossible to connect to %s', $server)); return 0; } // ensure enough execution time Safe::set_time_limit(30); // get server banner if (($reply = fgets($handle)) === FALSE) { Logger::remember('agents/messages.php: Impossible to get banner of ' . $server); fclose($handle); return 0; } if ($context['debug_messages'] == 'Y') { Logger::remember('agents/messages.php: POP <-', rtrim($reply), 'debug'); } // expecting an OK if (strncmp($reply, '+OK', 3)) { Logger::remember('agents/messages.php: Mail service is closed at ' . $server, rtrim($reply)); fclose($handle); return 0; } // maybe the server accepts APOP $stamp = ''; if (preg_match('/<.+@.+>/U', $reply, $matches)) { $stamp = $matches[0]; } // we will go with APOP, only if explicitly allowed $authenticated = FALSE; if ($stamp && preg_match('/\\bwith_apop\\b/i', $options)) { // the digest if ($context['debug_messages'] == 'Y') { Logger::remember('agents/messages.php: POP stamp', $stamp . $password, 'debug'); } $hash = md5($stamp . $password); // send user name and hash $request = 'APOP ' . $account . ' ' . $hash; fputs($handle, $request . CRLF); if ($context['debug_messages'] == 'Y') { Logger::remember('agents/messages.php: POP ->', $request, 'debug'); } // expecting an OK if (($reply = fgets($handle)) === FALSE) { Logger::remember('agents/messages.php: No reply to APOP command at ' . $server); fclose($handle); return 0; } if ($context['debug_messages'] == 'Y') { Logger::remember('agents/messages.php: POP <-', rtrim($reply), 'debug'); } if (strncmp($reply, '+OK', 3)) { Logger::remember('agents/messages.php: Impossible to authenticate account ' . $account . ' at ' . $server, rtrim($reply)); } else { $authenticated = TRUE; } } // we will transmit the password in clear if (!$authenticated) { // send user name $request = 'USER ' . $account; fputs($handle, $request . CRLF); if ($context['debug_messages'] == 'Y') { Logger::remember('agents/messages.php: POP ->', $request, 'debug'); } // expecting an OK if (($reply = fgets($handle)) === FALSE) { Logger::remember('agents/messages.php: No reply to USER command at ' . $server); fclose($handle); return 0; } if ($context['debug_messages'] == 'Y') { Logger::remember('agents/messages.php: POP <-', rtrim($reply), 'debug'); } if (strncmp($reply, '+OK', 3)) { Logger::remember('agents/messages.php: Unknown account ' . $account . ' at ' . $server, rtrim($reply)); fclose($handle); return 0; } // send password $request = 'PASS ' . $password; fputs($handle, $request . CRLF); if ($context['debug_messages'] == 'Y') { Logger::remember('agents/messages.php: POP ->', $request, 'debug'); } // expecting an OK if (($reply = fgets($handle)) === FALSE) { Logger::remember('agents/messages.php: No reply to PASS command at ' . $server); fclose($handle); return 0; } if ($context['debug_messages'] == 'Y') { Logger::remember('agents/messages.php: POP <-', rtrim($reply), 'debug'); } if (strncmp($reply, '+OK', 3)) { Logger::remember('agents/messages.php: Invalid password for account ' . $account . ' at ' . $server, rtrim($reply)); fclose($handle); return 0; } } // ask for information $request = 'STAT'; fputs($handle, $request . CRLF); if ($context['debug_messages'] == 'Y') { Logger::remember('agents/messages.php: POP ->', $request, 'debug'); } // expecting an OK if (($reply = fgets($handle)) === FALSE) { Logger::remember('agents/messages.php: No reply to STAT command at ' . $server); fclose($handle); return 0; } if (strncmp($reply, '+OK', 3)) { Logger::remember('agents/messages.php: Rejected command STAT at ' . $server, 'reply="' . rtrim($reply) . '"'); fclose($handle); return 0; } // evaluate queue size $tokens = explode(' ', $reply); if ($context['debug_messages'] == 'Y') { Logger::remember('agents/messages.php: POP <-', rtrim($reply), 'debug'); } $queue_size = @$tokens[1]; // nothing to do if (!$queue_size) { fclose($handle); return 0; } // limit the number of messages processed on each tick if ($queue_size > 10) { $queue_size = 10; } // process messages one by one for ($index = 1; $index <= $queue_size; $index++) { // ask for the message $request = 'RETR ' . $index; fputs($handle, $request . CRLF); if ($context['debug_messages'] == 'Y') { Logger::remember('agents/messages.php: POP ->', $request, 'debug'); } // expecting an OK if (($reply = fgets($handle)) === FALSE) { Logger::remember('agents/messages.php: No reply to RETR command at ' . $server); fclose($handle); return $index - 1; } if (strncmp($reply, '+OK', 3)) { Logger::remember('agents/messages.php: Rejected command RETR at ' . $server, rtrim($reply)); fclose($handle); return $index - 1; } // fetch one message at a time $message = ''; while (!feof($handle)) { // ensure enough execution time Safe::set_time_limit(30); // get a chunk (up to ten 1500-byte Ethernet packets) $chunk = fread($handle, 16384); // look for message end if (preg_match("/(.*)\\.\r\n\$/s", $chunk, $matches)) { $message .= $matches[1]; break; } // not yet at the end $message .= $chunk; } // suppress the message from the mailbox before entering into the database $request = 'DELE ' . $index; fputs($handle, $request . CRLF); if ($context['debug_messages'] == 'Y') { Logger::remember('agents/messages.php: POP ->', $request, 'debug'); } // expecting an OK if (($reply = fgets($handle)) === FALSE) { Logger::remember('agents/messages.php: No reply to DELE command at ' . $server); } elseif (strncmp($reply, '+OK', 3)) { Logger::remember('agents/messages.php: Rejected command DELE at ' . $server, rtrim($reply)); } // file the message if in debug mode if ($context['debug_messages'] == 'Y' && Safe::make_path('temporary/agents')) { Safe::file_put_contents('temporary/agents/' . uniqid('message_'), $message); } // process the message Messages::process_message($message); } // close the session to actually purge the queue $request = 'QUIT'; fputs($handle, $request . CRLF); if ($context['debug_messages'] == 'Y') { Logger::remember('agents/messages.php: POP ->', $request, 'debug'); } // expecting an OK if (($reply = fgets($handle)) === FALSE) { Logger::remember('agents/messages.php: No reply to QUIT command at ' . $server); } elseif (strncmp($reply, '+OK', 3)) { Logger::remember('agents/messages.php: Rejected command QUIT at ' . $server, rtrim($reply)); } if ($queue_size > 0) { Logger::remember('agents/messages.php: ' . $queue_size . ' message(s) have been processed from ' . $server); } fclose($handle); return $queue_size; }
/** * transform some text to load related images * * @param string the input text * @return the tansformed text */ public static function render_smileys($text) { global $context; // no content on HEAD request --see scripts/validate.php if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] == 'HEAD') { return $text; } // sanity check $text = trim($text); if (!$text) { return $text; } // the list of codes to be interpreted --initialize only once static $pattern, $replace; if (!isset($pattern)) { $pattern = array(); $replace = array(); $prefix = ' <img src="' . $context['url_to_root'] . 'skins/_reference/smileys/'; $suffix = '" alt="" /> '; $pattern[] = '>:('; $replace[] = $prefix . 'angry.gif' . $suffix; $pattern[] = ':-D'; $replace[] = $prefix . 'cheesy.gif' . $suffix; $pattern[] = " :'("; $replace[] = $prefix . 'cry.gif' . $suffix; $pattern[] = ":'-("; $replace[] = $prefix . 'cry.gif' . $suffix; $pattern[] = '8-)'; $replace[] = $prefix . 'cool.gif' . $suffix; $pattern[] = ':-('; $replace[] = $prefix . 'frown.gif' . $suffix; $pattern[] = '???'; $replace[] = $prefix . 'confused.gif' . $suffix; $pattern[] = ':-['; $replace[] = $prefix . 'embarassed.gif' . $suffix; $pattern[] = ':blush:'; $replace[] = $prefix . 'blushing.gif' . $suffix; $pattern[] = ':-X'; $replace[] = $prefix . 'sealed.gif' . $suffix; $pattern[] = ':-P'; $replace[] = $prefix . 'tongue.gif' . $suffix; $pattern[] = ':medal:'; $replace[] = $prefix . 'medal_full.gif' . $suffix; $pattern[] = ':half_medal:'; $replace[] = $prefix . 'medal_half.gif' . $suffix; $pattern[] = '::-)'; $replace[] = $prefix . 'rolleyes.gif' . $suffix; $pattern[] = ' :)'; $replace[] = $prefix . 'smile.gif' . $suffix; $pattern[] = ':-)'; $replace[] = $prefix . 'smile.gif' . $suffix; $pattern[] = ':-o'; $replace[] = $prefix . 'shocked.gif' . $suffix; $pattern[] = ' :/'; $replace[] = $prefix . 'undecided.gif' . $suffix; $pattern[] = ':-/'; $replace[] = $prefix . 'undecided.gif' . $suffix; $pattern[] = ' ;)'; $replace[] = $prefix . 'winkgrin.gif' . $suffix; $pattern[] = ';-)'; $replace[] = $prefix . 'winkgrin.gif' . $suffix; $pattern[] = ':party:'; $replace[] = $prefix . 'partygirl.gif' . $suffix; $pattern[] = ':*:'; $replace[] = $prefix . 'star.gif' . $suffix; $pattern[] = ' :*'; $replace[] = $prefix . 'kiss.gif' . $suffix; $pattern[] = ':-*'; $replace[] = $prefix . 'kiss.gif' . $suffix; $pattern[] = ' :+'; $replace[] = $prefix . 'thumbsup.gif' . $suffix; $pattern[] = ':up:'; $replace[] = $prefix . 'thumbsup.gif' . $suffix; $pattern[] = ' :-'; $replace[] = $prefix . 'thumbsdown.gif' . $suffix; $pattern[] = ':down:'; $replace[] = $prefix . 'thumbsdown.gif' . $suffix; $pattern[] = ':?!'; $replace[] = $prefix . 'idea.gif' . $suffix; $pattern[] = ' :?2'; $replace[] = $prefix . 'question2.gif' . $suffix; $pattern[] = ' :?'; $replace[] = $prefix . 'question.gif' . $suffix; $pattern[] = ' :!2'; $replace[] = $prefix . 'exclamation2.gif' . $suffix; $pattern[] = ' :!'; $replace[] = $prefix . 'exclamation.gif' . $suffix; } // ensure we have enough processing time Safe::set_time_limit(30); // process dotted smileys --insert a space for smileys at the very beginning of the string $text = str_replace($pattern, $replace, ' ' . $text); // process any image file $text = preg_replace_callback('/:([\\w_]+):/', array('smileys', 'parse_match'), $text); return $text; }
$context['text'] .= Skin::build_block(sprintf(i18n::s('Analyzing table %s...'), SQL::table_name('comments')), 'title'); // scan up to 20000 items $count = 0; $query = "SELECT id, anchor FROM " . SQL::table_name('comments') . " ORDER BY anchor LIMIT 0, 100000"; if (!($result = SQL::query($query))) { return; } else { // fetch one anchor and the linked member $errors_count = 0; while ($row = SQL::fetch($result)) { // animate user screen and take care of time $count++; if (!($count % 500)) { $context['text'] .= sprintf(i18n::s('%d records have been processed'), $count) . BR . "\n"; // ensure enough execution time Safe::set_time_limit(30); } // check that the anchor exists, if any if ($row['anchor'] && !Anchors::get($row['anchor'])) { $context['text'] .= sprintf(i18n::s('Orphan: %s'), 'comment ' . Skin::build_link(Comments::get_url($row['id']), $row['id'])) . BR . "\n"; if (++$errors_count >= 5) { $context['text'] .= i18n::s('Too many successive errors. Aborted') . BR . "\n"; break; } } else { $errors_count = 0; } } } // ending message $context['text'] .= sprintf(i18n::s('%d records have been processed'), $count) . BR . "\n";
/** * validate a link * * This function submits a HTTP request to the target server to check that the page actually exists * * @param the link to validate * @return A date if Last-Modified has been provided, or TRUE if the link is reachable, FALSE otherwise */ function validate($url) { global $context; // parse this url $items = @parse_url($url); // assume the link is correct if not http if ($items['scheme'] && $items['scheme'] != 'http') { return TRUE; } // no host, assume it's us if (!($host = $items['host'])) { $host = $context['host_name']; } // sometime parse_url() adds a '_' $host = rtrim($host, '_'); // no port, assume the standard if (!($port = $items['port'])) { $port = 80; } // assume the link is correct when outbound web is not authorized if (isset($context['without_outbound_http']) && $context['without_outbound_http'] == 'Y') { return TRUE; } // open a network connection -- wait for up to 10 seconds for the TCP connection if (!($handle = Safe::fsockopen($host, $port, $errno, $errstr, 10))) { if ($context['with_debug'] == 'Y') { logger::remember('links/link.php: ' . $host . ':' . $port . ' is not reachable', $url, 'debug'); } return FALSE; } // ensure enough execution time Safe::set_time_limit(30); // build the path $path = $items['path']; if (!$path) { $path = '/'; } // sometime parse_url() adds a '_' $path = rtrim($path, '_'); // include any query if ($items['query']) { $path .= '?' . $items['query']; } // send an HTTP request fputs($handle, 'HEAD ' . $path . " HTTP/1.0" . CRLF . 'Host: ' . $host . CRLF . "User-Agent: YACS (www.yacs.fr)" . CRLF . "Connection: close" . CRLF . CRLF); // we are interested into the header only $response = ''; while (!feof($handle) && strlen($response) < 5242880) { // ask for Ethernet-sized chunks $chunk = fread($handle, 1500); // split on headers boundary $here = strpos($chunk, CRLF . CRLF); if ($here !== FALSE) { $chunk = substr($chunk, 0, $here); $response .= $chunk; break; } // gather header information $response .= $chunk; } fclose($handle); // split headers into lines $lines = explode(CRLF, $response); // ensure we have a valid HTTP status line if (!preg_match('/^HTTP\\/[0-9\\.]+ 20\\d /', $lines[0])) { if ($context['with_debug'] == 'Y') { logger::remember('links/link.php: bad status: ' . $lines[0], $url, 'debug'); } return FALSE; } // scan lines for "Last-Modified" header foreach ($lines as $line) { if (preg_match('/^Last-Modified: (.*?)/', $line, $matches)) { // return the stamp for this link return date("Y-m-d H:i:s", strtotime($matches[1])); } } // no date, but the link has been validated anyway return TRUE; }
/** * transcode multi-byte characters to HTML representations for Unicode * * This function is aiming to preserve Unicode characters through storage in a ISO-8859-1 compliant system. * * Every multi-byte UTF-8 character is transformed to its equivalent HTML numerical entity (eg, &#4568;) * that may be handled safely by PHP and by MySQL. * * Of course, this solution does not allow for full-text search in the database and therefore, is not a * definitive solution to internationalization issues. * It does enable, however, practical use of Unicode to build pages in foreign languages. * * Also, this function transforms HTML entities into their equivalent Unicode entities. * For example, w.bloggar posts pages using HTML entities. * If you have to modify these pages using web forms, you would like to get UTF-8 instead. * * @link http://www.evolt.org/article/A_Simple_Character_Entity_Chart/17/21234/ A Simple Character Entity Chart * * @param mixed the original UTF-8 string, or an array * @return a string acceptable in an ISO-8859-1 storage system (ie., PHP4 + MySQL 3) */ public static function &to_unicode($input) { global $context; // transcode arrays as well if (is_array($input)) { utf8::to_unicode_recursively($input); $output = $input; return $output; } // scan the whole string $output = ''; $index = 0; $tick = 0; while ($index < strlen($input)) { // for jumbo pages --observed 167 seconds processing time for 414kbyte input $tick++; if (!($tick % 25000)) { Safe::set_time_limit(30); } // look at one char $char = ord($input[$index]); // one byte (0xxxxxxx) if ($char < 0x80) { // some chars may be undefined $output .= chr($char); $index += 1; // two bytes (110xxxxx 10xxxxxx) } elseif ($char < 0xe0) { // strip weird sequences (eg, C0 80 -> NUL) if ($value = $char % 0x20 * 0x40 + ord($input[$index + 1]) % 0x40) { $output .= '&#' . $value . ';'; } $index += 2; // three bytes (1110xxxx 10xxxxxx 10xxxxxx) example: euro sign = \xE2\x82\xAC -> € } elseif ($char < 0xf0) { // strip weird sequences if ($value = $char % 0x10 * 0x1000 + ord($input[$index + 1]) % 0x40 * 0x40 + ord($input[$index + 2]) % 0x40) { $output .= '&#' . $value . ';'; } $index += 3; // four bytes (11110xxx 10xxxxxx 10xxxxxx 10xxxxxx) } elseif ($char < 0xf8) { // strip weird sequences if ($value = $char % 0x8 * 0x40000 + ord($input[$index + 1]) % 0x40 * 0x1000 + ord($input[$index + 2]) % 0x40 * 0x40 + ord($input[$index + 3]) % 0x40) { $output .= '&#' . $value . ';'; } $index += 4; // five bytes (111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx) } elseif ($char < 0xfc) { // strip weird sequences if ($value = $char % 0x4 * 0x1000000 + ord($input[$index + 1]) % 0x40 * 0x40000 + ord($input[$index + 2]) % 0x40 * 0x1000 + ord($input[$index + 3]) % 0x40 * 0x40 + ord($input[$index + 4]) % 0x40) { $output .= '&#' . $value . ';'; } $index += 5; // six bytes (1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx) } else { // strip weird sequences if ($value = $char % 0x2 * 0x40000000 + ord($input[$index + 1]) % 0x40 * 0x1000000 + ord($input[$index + 2]) % 0x40 * 0x40000 + ord($input[$index + 3]) % 0x40 * 0x1000 + ord($input[$index + 4]) % 0x40 * 0x40 + ord($input[$index + 4]) % 0x40) { $output .= '&#' . $value . ';'; } $index += 6; } } // transcode explicit unicode entities %u2019 -> ’ $output = preg_replace_callback('/%u([0-9a-z]{4})/is', function ($matches) { return '&#' . hexdec($matches[1]); }, $output); // transcode HTML entities to Unicode entities $output =& utf8::transcode($output); // translate extended ISO8859-1 chars, if any, to utf-8 $output = utf8_encode($output); // return the translated string return $output; }
/** * parse one script to build the php documentation * * @param string one script * @param the path to access the script * @return either NULL or an error message */ function parse($script, $path = 'scripts/reference/') { global $context, $page_count; // at least put the script name as a title $this->titles[$script] = $script; // read the file if (!($handle = Safe::fopen($path . $script, 'rb'))) { $this->comments[$script] = sprintf(i18n::s('Impossible to read %s.'), $script); return sprintf(i18n::s('Impossible to read %s.'), $context['path_to_root'] . $path . $script); } // locate php comments $comment = array(); global $first_comment; $first_comment = TRUE; $in_comment = FALSE; $count = 0; while (!feof($handle)) { // up to 4k per line $line = fgets($handle, 4096); // ensure we have enough execution time $count++; if (!($count % 1000)) { Safe::set_time_limit(30); } // a comment ends if ($in_comment && preg_match('/\\s*\\*+\\//', $line)) { $in_comment = FALSE; // a comment continues } elseif ($in_comment) { // strip the '*' at the beginning of the line $comment[] = preg_replace('/^\\s*\\*\\s{0,1}/', '', $line); // comment begins } elseif (preg_match('/\\s*\\/\\*{2,}/', $line)) { $in_comment = TRUE; // class extension } elseif (preg_match('/^\\s*class\\s+(\\w+)\\s+extends\\s+(\\w+)/i', $line, $matches)) { $name = $matches[0]; $this->comment_block($script, $name, $comment); $comment = array(); // class definition } elseif (preg_match('/^\\s*class\\s+(\\w+)/i', $line, $matches)) { $name = $matches[0]; $this->comment_block($script, $name, isset($comment) ? $comment : ''); $comment = array(); // function definition } elseif (preg_match('/^\\s*function\\s+(&{0,1}\\w+)\\s*\\((.*)\\)/i', $line, $matches)) { $name = $matches[0]; $this->comment_block($script, $name, isset($comment) ? $comment : ''); $comment = array(); // only a comment } elseif (preg_match('/^\\s*\\/\\//', $line)) { // a blank line } elseif (preg_match('/^\\s*$/', $line)) { // not a declaration } elseif (@count($comment)) { $this->comment_block($script, '', $comment); $comment = array(); } } // ensure enough execution time Safe::set_time_limit(30); // generate the documentation page for this file $fields['name'] = $script; $fields['anchor'] = dirname($script); $fields['label'] = isset($this->index[$script]) ? $this->index[$script] : '*** you should add a phpDoc label line to this file'; $fields['content'] = isset($this->comments[$script]) ? "[toc]" . $this->comments[$script] : '*** you should expand phpDoc comments for this file'; $query = "INSERT INTO " . SQL::table_name('phpdoc') . " SET " . " name='" . SQL::escape($fields['name']) . "'," . " anchor='" . SQL::escape($fields['anchor']) . "'," . " label='" . SQL::escape($fields['label']) . "'," . " content='" . SQL::escape($fields['content']) . "'," . " edit_date='" . gmstrftime('%Y-%m-%d %H:%M:%S') . "'"; if (SQL::query($query, TRUE) === FALSE) { echo $query . BR . SQL::error() . BR . "\n"; } $page_count++; }
function _extractList($p_path, &$p_list_detail, $p_mode, $p_file_list, $p_remove_path) { $v_result = true; $v_nb = 0; $v_extract_all = true; $v_listing = false; $p_path = $this->_translateWinPath($p_path, false); if ($p_path == '' || substr($p_path, 0, 1) != '/' && substr($p_path, 0, 3) != "../" && !strpos($p_path, ':')) { $p_path = "./" . $p_path; } $p_remove_path = $this->_translateWinPath($p_remove_path); // ----- Look for path to remove format (should end by /) if ($p_remove_path != '' && substr($p_remove_path, -1) != '/') { $p_remove_path .= '/'; } $p_remove_path_size = strlen($p_remove_path); switch ($p_mode) { case "complete": $v_extract_all = TRUE; $v_listing = FALSE; break; case "partial": $v_extract_all = FALSE; $v_listing = FALSE; break; case "list": $v_extract_all = FALSE; $v_listing = TRUE; break; default: $this->_error('Invalid extract mode (' . $p_mode . ')'); return false; } clearstatcache(); while (strlen($v_binary_data = $this->_readBlock()) != 0) { $v_extract_file = FALSE; $v_extraction_stopped = 0; if (!$this->_readHeader($v_binary_data, $v_header)) { return false; } if ($v_header['filename'] == '') { continue; } // ----- Look for long filename if ($v_header['typeflag'] == 'L') { if (!$this->_readLongHeader($v_header)) { return false; } } if (!$v_extract_all && is_array($p_file_list)) { // ----- By default no unzip if the file is not found $v_extract_file = false; for ($i = 0; $i < sizeof($p_file_list); $i++) { // ----- Look if it is a directory if (substr($p_file_list[$i], -1) == '/') { // ----- Look if the directory is in the filename path if (strlen($v_header['filename']) > strlen($p_file_list[$i]) && substr($v_header['filename'], 0, strlen($p_file_list[$i])) == $p_file_list[$i]) { $v_extract_file = TRUE; break; } } elseif ($p_file_list[$i] == $v_header['filename']) { $v_extract_file = TRUE; break; } } } else { $v_extract_file = TRUE; } // ----- Look if this file need to be extracted if ($v_extract_file && !$v_listing) { if ($p_remove_path != '' && substr($v_header['filename'], 0, $p_remove_path_size) == $p_remove_path) { $v_header['filename'] = substr($v_header['filename'], $p_remove_path_size); } if ($p_path != './' && $p_path != '/') { while (substr($p_path, -1) == '/') { $p_path = substr($p_path, 0, strlen($p_path) - 1); } if (substr($v_header['filename'], 0, 1) == '/') { $v_header['filename'] = $p_path . $v_header['filename']; } else { $v_header['filename'] = $p_path . '/' . $v_header['filename']; } } if (file_exists($v_header['filename'])) { if (@is_dir($v_header['filename']) && $v_header['typeflag'] == '') { $this->_error('File ' . $v_header['filename'] . ' already exists as a directory'); return false; } if ($this->_isArchive($v_header['filename']) && $v_header['typeflag'] == "5") { $this->_error('Directory ' . $v_header['filename'] . ' already exists as a file'); return false; } if (!is_writeable($v_header['filename'])) { $this->_error('File ' . $v_header['filename'] . ' already exists and is write protected'); return false; } if (filemtime($v_header['filename']) > $v_header['mtime']) { // To be completed : An error or silent no replace ? } } elseif (($v_result = $this->_dirCheck($v_header['typeflag'] == "5" ? $v_header['filename'] : dirname($v_header['filename']))) != 1) { $this->_error('Unable to create path for ' . $v_header['filename']); return false; } if ($v_extract_file) { if ($v_header['typeflag'] == "5") { if (!@file_exists($v_header['filename'])) { global $context; if (!@mkdir($v_header['filename'], $context['directory_mask'])) { $this->_error('Unable to create directory {' . $v_header['filename'] . '}'); return false; } } } else { if (($v_dest_file = @fopen($v_header['filename'], "wb")) == 0) { $this->_error('Error while opening {' . $v_header['filename'] . '} in write binary mode'); return false; } else { $n = floor($v_header['size'] / 512); for ($i = 0; $i < $n; $i++) { $v_content = $this->_readBlock(); fwrite($v_dest_file, $v_content, 512); } if ($v_header['size'] % 512 != 0) { $v_content = $this->_readBlock(); fwrite($v_dest_file, $v_content, $v_header['size'] % 512); } @fclose($v_dest_file); // ----- Change the file mode, mtime @touch($v_header['filename'], $v_header['mtime']); // To be completed Safe::chmod($v_header['filename']); } // ----- Check the file size clearstatcache(); if (filesize($v_header['filename']) != $v_header['size']) { $this->_error('Extracted file ' . $v_header['filename'] . ' does not have the correct file size \'' . filesize($v_header['filename']) . '\' (' . $v_header['size'] . ' expected). Archive may be corrupted.'); return false; } } } else { $this->_jumpBlock(ceil($v_header['size'] / 512)); } } else { $this->_jumpBlock(ceil($v_header['size'] / 512)); } /* TBC : Seems to be unused ... if ($this->_compress) $v_end_of_file = @gzeof($this->_file); else $v_end_of_file = @feof($this->_file); */ if ($v_listing || $v_extract_file || $v_extraction_stopped) { // ----- Log extracted files if (($v_file_dir = dirname($v_header['filename'])) == $v_header['filename']) { $v_file_dir = ''; } if (substr($v_header['filename'], 0, 1) == '/' && $v_file_dir == '') { $v_file_dir = '/'; } $p_list_detail[$v_nb++] = $v_header; } Safe::set_time_limit(30); } return true; }
/** * process deferred messages * * Most often, the server has to stay below a given rate of messages, * for example 50 messages per hour. * * Of course, any lively community will feature bursts of activity and of * messages, therefore the need for a shaping mechanism. * * YACS implements a leaking bucket algorithm to take care of messages sent * previously: * * 1. Initially, the bucket is empty. * * 2. New messages are queued in the database, to be processed asynchronously. * * 3. On background ticks, the bucket is decremented. If the bucket becomes * empty, and if some messages have been queued, a couple of them are sent, and * the bucket is incremented accordingly. * * Bucket content is managed as value 'bucket.content' saved in the database. * * The bucket size is given by parameter $context['mail_hourly_maximum'], set * in the configuration panel for system parameters. * * This parameter has a default value of 50, meaning YACS will not send more * than 50 messages per hour. * * Background processing is either added to regular page generation or delegated * to an external sub-system (e.g., cron). In case of a large site, we recommend * to use the second solution, even if this adds additional setup steps. Your * choice will be recorded in the configuration panel for system parameters. * * @see control/configure.php * * The number of messages sent on each tick can go up to the bucket size if * background processing is external. Else it is one fourth of bucket size, to * minimize impact on watching surfer. * * @see cron.php */ public static function tick_hook() { global $context; // email services have to be activated if (!isset($context['with_email']) || $context['with_email'] != 'Y') { return; } // useless if we don't have a valid database connection if (!$context['connection']) { return; } // remember start time $start = get_micro_time(); // get bucket size --force it if set to 0 if (!isset($context['mail_hourly_maximum']) || $context['mail_hourly_maximum'] < 5) { $context['mail_hourly_maximum'] = 50; } // get record related to last tick include_once $context['path_to_root'] . 'shared/values.php'; $bucket = Values::get_record('mailer.bucket.content', 0); $bucket['value'] = intval($bucket['value']); // some content to leak if ($bucket['value'] > 0) { // date of last stamp if (isset($bucket['edit_date'])) { $stamp = SQL::strtotime($bucket['edit_date']); } else { $stamp = time() - 3600; } // leak is maximum after one hour $leak = intval($context['mail_hourly_maximum'] * (time() - $stamp) / 3600); // preserve previous value until actual leak if ($leak < 1) { return; } // actual leak $bucket['value'] = max(0, $bucket['value'] - $leak); } // process some messages only when bucket is empty $count = 0; if ($bucket['value'] < 1) { // reduced speed if on-line processing if (isset($_SERVER['REMOTE_ADDR'])) { $slice = intval($context['mail_hourly_maximum'] / 4); } else { $slice = intval($context['mail_hourly_maximum']); } // get some messages, if any $query = "SELECT * FROM " . SQL::table_name('messages') . " ORDER BY edit_date LIMIT 0, " . $slice; if ($result = SQL::query($query)) { // process every message while ($item = SQL::fetch($result)) { Mailer::process($item['recipient'], $item['subject'], $item['message'], $item['headers']); // purge the queue $query = 'DELETE FROM ' . SQL::table_name('messages') . ' WHERE id = ' . $item['id']; SQL::query($query); // fill the bucket $bucket['value'] += 1; $count++; // take care of time if (!($count % 50)) { // ensure enough execution time Safe::set_time_limit(30); } } // close connection Mailer::close(); } } // remember new state of the bucket Values::set('mailer.bucket.content', $bucket['value']); // compute execution time $time = round(get_micro_time() - $start, 2); // report on work achieved if ($count > 1) { return 'shared/mailer.php: ' . $count . ' messages have been processed (' . $time . ' seconds)' . BR; } elseif ($count == 1) { return 'shared/mailer.php: 1 message has been processed (' . $time . ' seconds)' . BR; } elseif ($bucket['value']) { return 'shared/mailer.php: delaying messages (' . $time . ' seconds)' . BR; } else { return 'shared/mailer.php: nothing to do (' . $time . ' seconds)' . BR; } }
function parse_tag_close($parser, $tag) { global $context; global $in_overlay, $overlay_class, $overlay_parameters; global $parsed_cdata, $parsed_item, $parsed_overlay, $parsing_report; // save gathered data if necessary switch ($tag) { case 'article': // end of article // transcode owner id $parsed_item['owner_id'] = Surfer::get_id(); if (isset($parsed_item['owner_nick_name']) && ($user = Users::get($parsed_item['owner_nick_name']))) { $parsed_item['owner_id'] = $user['id']; } // transcode creator id $parsed_item['create_id'] = Surfer::get_id(); if (isset($parsed_item['create_nick_name']) && ($user = Users::get($parsed_item['create_nick_name']))) { $parsed_item['create_id'] = $user['id']; } // transcode editor id $parsed_item['edit_id'] = Surfer::get_id(); if (isset($parsed_item['edit_nick_name']) && ($user = Users::get($parsed_item['edit_nick_name']))) { $parsed_item['edit_id'] = $user['id']; } // transcode publisher id $parsed_item['publish_id'] = Surfer::get_id(); if (isset($parsed_item['publish_nick_name']) && ($user = Users::get($parsed_item['publish_nick_name']))) { $parsed_item['publish_id'] = $user['id']; } // bind to given overlay $overlay = NULL; if ($overlay_class) { $overlay = Overlay::bind($overlay_class . ' ' . $overlay_parameters); } // when the page has been overlaid if (is_object($overlay)) { // update the overlay from content foreach ($parsed_overlay as $label => $value) { $overlay->attributes[$label] = $value; } // save content of the overlay in this item $parsed_item['overlay'] = $overlay->save(); $parsed_item['overlay_id'] = $overlay->get_id(); } // find anchor from handle if (isset($parsed_item['anchor_handle']) && ($reference = Sections::lookup($parsed_item['anchor_handle']))) { $parsed_item['anchor'] = $reference; } // update an existing page if (isset($parsed_item['handle']) && ($item = Articles::get($parsed_item['handle']))) { // transcode page id $parsed_item['id'] = $item['id']; // stop on error if (!Articles::put($parsed_item) || is_object($overlay) && !$overlay->remember('update', $parsed_item, 'article:' . $item['id'])) { Logger::error(sprintf('Unable to save article %s', $parsed_item['title'] . ' (' . $parsed_item['id'] . ')')); } // create a new page } else { unset($parsed_item['id']); // stop on error if (!($parsed_item['id'] = Articles::post($parsed_item))) { Logger::error(sprintf('Unable to save article %s', $parsed_item['title'])); } else { // save overlay content if (is_object($overlay)) { $overlay->remember('insert', $parsed_item, 'article:' . $parsed_item['id']); } } } // report to surfer $parsing_report .= '<li>' . Skin::build_link(Articles::get_permalink($parsed_item), $parsed_item['title']) . "</li>\n"; // ready for next item $overlay_class = NULL; $overlay_parameters = ''; $parsed_overlay = array(); $parsed_item = array(); Safe::set_time_limit(30); break; case 'overlay': // end of overlay data $in_overlay = FALSE; break; case 'section': // end of section // transcode owner id $parsed_item['owner_id'] = Surfer::get_id(); if (isset($parsed_item['owner_nick_name']) && ($user = Users::get($parsed_item['owner_nick_name']))) { $parsed_item['owner_id'] = $user['id']; } // transcode creator id $parsed_item['create_id'] = Surfer::get_id(); if (isset($parsed_item['create_nick_name']) && ($user = Users::get($parsed_item['create_nick_name']))) { $parsed_item['create_id'] = $user['id']; } // transcode editor id $parsed_item['edit_id'] = Surfer::get_id(); if (isset($parsed_item['edit_nick_name']) && ($user = Users::get($parsed_item['edit_nick_name']))) { $parsed_item['edit_id'] = $user['id']; } // bind to given overlay $overlay = NULL; if ($overlay_class) { $overlay = Overlay::bind($overlay_class . ' ' . $overlay_parameters); } // when the page has been overlaid if (is_object($overlay)) { // update the overlay from content foreach ($parsed_overlay as $label => $value) { $overlay->attributes[$label] = $value; } // save content of the overlay in this item $parsed_item['overlay'] = $overlay->save(); $parsed_item['overlay_id'] = $overlay->get_id(); } // find anchor from handle if (isset($parsed_item['anchor_handle']) && ($reference = Sections::lookup($parsed_item['anchor_handle']))) { $parsed_item['anchor'] = $reference; } // update an existing section if (isset($parsed_item['handle']) && ($item = Sections::get($parsed_item['handle']))) { // transcode section id $parsed_item['id'] = $item['id']; // stop on error if (!Sections::put($parsed_item) || is_object($overlay) && !$overlay->remember('update', $parsed_item, 'section:' . $item['id'])) { Logger::error(sprintf('Unable to save section %s', $parsed_item['title'] . ' (' . $parsed_item['id'] . ')')); } // create a new page } else { unset($parsed_item['id']); // stop on error if (!($parsed_item['id'] = Sections::post($parsed_item))) { Logger::error(sprintf('Unable to save section %s', $parsed_item['title'])); } else { // save overlay content if (is_object($overlay)) { $overlay->remember('insert', $parsed_item, 'section:' . $parsed_item['id']); } } } // report to surfer $parsing_report .= '<li>' . Skin::build_link(Sections::get_permalink($parsed_item), $parsed_item['title']) . "</li>\n"; // ready for next item $overlay_class = NULL; $overlay_parameters = ''; $parsed_overlay = array(); $parsed_item = array(); Safe::set_time_limit(30); break; default: // just another attribute // decode cdata $parsed_cdata = trim(preg_replace(array('/</', '/>/'), array('<', '>'), $parsed_cdata)); // feeding the overlay or the item itself if ($in_overlay) { $parsed_overlay[$tag] = $parsed_cdata; } else { $parsed_item[$tag] = $parsed_cdata; } // ready for next attribute $parsed_cdata = ''; break; } }
function index_keywords($file_name) { // parse keywords in some files if (preg_match('/(\\.txt|\\.doc|\\.xls)$/i', $file_name) && ($handle = Safe::fopen($file_name, 'rb')) !== FALSE) { // load noise words Safe::load('files/noise_words.php'); // use chunks of 50 kbytes $filtered_words = array(); while (count($noise_words) && ($buffer = fread($handle, 51200))) { // strip binary stuff $buffer = preg_replace("/[вда]/m", 'a', $buffer); $buffer = preg_replace("/[йкли]/m", 'e', $buffer); $buffer = preg_replace("/[оп]/m", 'i', $buffer); $buffer = preg_replace("/[фц]/m", 'o', $buffer); $buffer = preg_replace("/[ыь]/m", 'u', $buffer); $buffer = str_replace('з', 'c', $buffer); $buffer = preg_replace('/[^a-zA-Z_0-9]+/m', ' ', $buffer); // ensure enough execution time Safe::set_time_limit(30); // strip html-like things $buffer = strip_tags($buffer); $buffer = preg_replace('/&\\w;/m', '', $buffer); // ensure enough execution time Safe::set_time_limit(30); // extract all readable words // $context['debug'][] = 'buffer=<pre>'.$buffer.'</pre>'; $words = preg_split("/[\\s]+/", $buffer); // $context['debug'][] = count($words).' words extracted'; // ensure enough execution time Safe::set_time_limit(30); // filter words foreach ($words as $word) { // mysql does not index words of less than 3 chars $length = strlen($word); if ($length <= 3 || $length > 25) { continue; } if (preg_match('/[0-9]/', $word)) { continue; } if (preg_match('/^[_0-9]/', $word)) { continue; } // filter words against the list of noise words $word = strtolower($word); if (!in_array($word, $noise_words)) { $filtered_words[$word] += 1; } } // ensure enough execution time Safe::set_time_limit(30); } // the complete file has been read fclose($handle); // ensure enough execution time Safe::set_time_limit(30); // memorize up to 1000 keywords if (is_array($filtered_words)) { ksort($filtered_words); reset($filtered_words); $keywords = ''; if (is_array($filtered_words)) { foreach ($filtered_words as $word => $count) { $keywords .= $word . ' '; if ($keywords_count++ > 1000) { break; } } } } // ensure enough execution time Safe::set_time_limit(30); } return $keywords; }
/** * Internal function to process text replacement according * to codes' patterns. * uses preg_replace_callback and do the following treatment with priority : * 1. try to find a function among loaded script ( could be this class, or Skin class) * 2. try to find a class within codes extensions (in /codes/code_*.php) to perform the rendering * 3. perform a regular preg_replace * 4. let the text as is * * @global array $context * @param string $text to transform * @param array $patterns_map all the patterns to check and action to do with them * @return string transformed text. */ private static function process($text, $patterns_map) { global $context; // ensure we have enough time to execute Safe::set_time_limit(30); foreach ($patterns_map as $pattern => $action) { // use lowercase, we may look for a file with this $action = strtolower($action); // use of preg_replace_callback with an anonymous function $text = preg_replace_callback($pattern, function ($matches) use($pattern, $action, $context) { // returned text $replace = ''; // function to call $func = ''; // array of captured element $capture = array_slice($matches, 1); // test if mapped action is a callable function (case 1) if (is_callable($action)) { $func = $action; // test if map is a class } elseif (Safe::filesize('codes/' . $action . '.php')) { // delegate rendering to an extension (case 2) include_once $context['path_to_root'] . 'codes/' . $action . '.php'; $code = new $action(); $replace = $code->render($capture); unset($code); return $replace; } if ($func) { // call of class Codes method, with or without parameters (case 1) if (count($capture)) { $replace .= call_user_func_array($func, $capture); } else { $replace .= call_user_func($func); } } else { // regular preg_replace (case 3 and 4) $replace .= preg_replace($pattern, $action, $matches[0]); } return $replace; }, $text); } return $text; }
/** * delete staging files * * @param string the directory to start with * @see scripts/update.php */ function delete_staging($path) { global $context; $path_translated = str_replace('//', '/', $context['path_to_root'] . '/scripts/staging' . $path); if ($handle = Safe::opendir($path_translated)) { while (($node = Safe::readdir($handle)) !== FALSE) { if ($node == '.' || $node == '..') { continue; } // make a real name $target = str_replace('//', '/', $path . '/' . $node); $target_translated = str_replace('//', '/', $path_translated . '/' . $node); // delete sub directory content if (is_dir($target_translated)) { delete_staging($target); Safe::rmdir($target_translated); // delete all files } else { $context['text'] .= sprintf(i18n::s('Deleting %s'), '/scripts/staging' . $target) . BR . "\n"; Safe::unlink($target_translated); global $deleted_nodes; $deleted_nodes++; } // ensure we have enough time Safe::set_time_limit(30); } Safe::closedir($handle); } }
/** * query the database * * This function populates the error context, where applicable. * * @param string the SQL query * @param boolean optional TRUE to not report on any error * @param resource connection to be considered, if any * @return the resource returned by the database server, or the number of affected rows, or FALSE on error */ public static function query(&$query, $silent = FALSE, $connection = NULL) { global $context; // allow for reference $output = FALSE; // use the default connection if (!$connection) { // we do need a connection to the database if (!isset($context['connection']) || !$context['connection']) { return $output; } $connection = $context['connection']; } // reopen a connection if database is not reachable anymore if (get_micro_time() - $context['start_time'] > 1.0 && !SQL::ping($connection)) { // remember the error, if any -- we may not have a skin yet if (!$silent) { if (is_callable(array('Skin', 'error'))) { Logger::error(i18n::s('Connection to the database has been lost')); } else { die(i18n::s('Connection to the database has been lost')); } } // query cannot be processed return $output; } // ensure enough execution time Safe::set_time_limit(30); // profile database requests $query_stamp = get_micro_time(); // do the job if (is_callable('mysqli_query')) { $result = mysqli_query($connection, $query); } else { $result = mysql_query($query, $connection); } // finalize result if ($result) { // provide more than a boolean result if ($result === TRUE) { if (is_callable('mysqli_affected_rows')) { $result = mysqli_affected_rows($connection); } else { $result = mysql_affected_rows($connection); } } // flag slow requests $duration = get_micro_time() - $query_stamp; if ($duration >= 0.5 && $context['with_debug'] == 'Y') { Logger::remember('shared/sql.php: SQL::query() slow request', $duration . "\n\n" . $query, 'debug'); } // return the set of selected rows return $result; } // remember the error, if any if (SQL::errno($connection)) { // display some error message if (!$silent) { if (is_callable(array('Skin', 'error'))) { Logger::error($query . '<br />' . SQL::error($connection)); } else { die($query . '<br />' . SQL::error($connection)); } } // log the error at development host if ($context['with_debug'] == 'Y') { Logger::remember('shared/sql.php: SQL::query()', SQL::error($connection) . "\n\n" . $query, 'debug'); } } // no valid result return $output; }
/** * called from within a preg_replace_callback() in Article::parse() * */ function parse_match($matches) { global $context; // useful if they are a lot of tags to process Safe::set_time_limit(30); switch ($matches[1]) { case 'anchor': $this->item['anchor'] = $matches[2]; break; case 'author': if ($user = Users::get($matches[2])) { $this->item['create_name'] = $user['nick_name']; $this->item['create_id'] = $user['id']; $this->item['create_address'] = $user['email']; $this->item['publish_name'] = $user['nick_name']; $this->item['publish_id'] = $user['id']; $this->item['publish_address'] = $user['email']; $this->item['edit_name'] = $user['nick_name']; $this->item['edit_id'] = $user['id']; $this->item['edit_address'] = $user['email']; } break; case 'blogid': case 'section': if ($section = Sections::get($matches[2])) { $this->item['anchor'] = 'section:' . $section['id']; } break; case 'introduction': if (isset($this->item['introduction'])) { $this->item['introduction'] .= $matches[2] . ' '; } else { $this->item['introduction'] = $matches[2] . ' '; } break; case 'source': $this->item['source'] = $matches[2]; break; case 'tags': // web form // web form case 'category': // xml-rpc // xml-rpc case 'categories': // legacy if (isset($this->item['tags'])) { $this->item['tags'] .= $matches[2] . ' '; } else { $this->item['tags'] = $matches[2] . ' '; } break; case 'title': if (isset($this->item['title'])) { $this->item['title'] .= $matches[2] . ' '; } else { $this->item['title'] = $matches[2] . ' '; } break; default: return $matches[0]; } return ''; }
/** * login * * The script checks provided name and password against remote server. * * This is done by posting the user name and the password * to the web server. * * @param string the nickname of the user * @param string the submitted password * @return TRUE on succesful authentication, FALSE othewise */ function login($name, $password) { global $context; // we need some parameters if (!isset($this->attributes['authenticator_parameters']) || !$this->attributes['authenticator_parameters']) { Logger::error(i18n::s('Please provide parameters to the authenticator.')); return FALSE; } // extract parameters $parameters = explode(" ", $this->attributes['authenticator_parameters']); // ensure a minimum number of parameters if (count($parameters) != 3) { Logger::error(i18n::s('Provide expected parameters to the REST POST authenticator.')); return FALSE; } // parse URL format if (!($url = $parameters[0])) { Logger::error(i18n::s('Wrong format of the URL target for the HTTP authenticator.')); return FALSE; } // prepare raw POST payload $payload = urlencode($parameters[1]) . "=" . urlencode($name) . "&" . urlencode($parameters[2]) . "=" . urlencode($password); // submit credentials to the authenticating server include_once $context['path_to_root'] . 'services/call.php'; // build an HTTP request $request = "POST " . $url . " HTTP/1.0" . CRLF . 'Host: ' . $host . CRLF . "Accept-Encoding: gzip" . CRLF . "User-Agent: YACS (www.yacs.fr)" . CRLF . "Connection: close" . CRLF . "Content-Type: application/x-www-form-urlencoded" . CRLF . "Content-Length: " . strlen($payload) . CRLF . CRLF . $payload; // parse the target URL $items = @parse_url($url); // no host, assume it's us if (!isset($items['host']) || !($host = $items['host'])) { $host = $context['host_name']; } // no port, assume the standard if (!isset($items['port']) || !($port = $items['port'])) { $port = 80; } // outbound web is not authorized if (isset($context['without_outbound_http']) && $context['without_outbound_http'] == 'Y') { Logger::error(i18n::s('Outbound HTTP is not authorized.')); return FALSE; } // connect to the server if (!($handle = Safe::fsockopen($host, $port, $errno, $errstr, 30))) { Logger::error(sprintf(i18n::s('Impossible to connect to %.'), $items['host'] . ':' . $items['port'])); return FALSE; } // ensure enough execution time Safe::set_time_limit(30); // build the path, including any query $path = $items['path']; if (!$path) { $path = '/'; } if (isset($items['query']) && $items['query']) { $path .= '?' . $items['query']; } // submit the request fputs($handle, $request); // get everything by Ethernet-sized chunks $response = ''; while (!feof($handle) && strlen($response) < 5242880) { $response .= fread($handle, 1500); } fclose($handle); // ensure we have a valid HTTP status line if (preg_match('/^HTTP\\/[0-9\\.]+ 200 /', $response)) { return TRUE; } // failed authentication return FALSE; }
/** * get a list of remote resources * * This function performs a REST call against a web services that provides a RSS-encoded response. * * Minimum example: * [php] * $result = Call::list_resources($url); * if(!$result[0]) * echo $result[1]; // error message * else * ... // use call result from $result[1] * [/php] * * @param string the url to use * @param array the parameters to transmit * @return an array of which the first value indicates call success or failure * * @see search.php */ public static function list_resources($url, $parameters = NULL) { global $context; // encode the request $data = ''; foreach ($parameters as $label => $value) { if ($data) { $data .= '&'; } $data .= urlencode($label) . '=' . urlencode($value); } $headers = ''; $headers .= 'Content-Type: application/x-www-form-urlencoded' . CRLF; $headers .= 'Content-Length: ' . strlen($data) . CRLF; // parse the target URL $items = @parse_url($url); // no host, assume it's us if (!($host = $items['host'])) { $host = $context['host_name']; } // no port, assume the standard if (!isset($items['port']) || !($port = $items['port'])) { $port = 80; } // outbound web is not authorized if (isset($context['without_outbound_http']) && $context['without_outbound_http'] == 'Y') { return array(FALSE, 'Outbound HTTP is not authorized.'); } // connect to the server if (!($handle = Safe::fsockopen($host, $port, $errno, $errstr, 30))) { return array(FALSE, sprintf('Impossible to connect to %s.', $host . ':' . $port)); } // ensure enough execution time Safe::set_time_limit(30); // build the path, including any query $path = $items['path']; if ($items['query']) { $path .= '?' . $items['query']; } // build an HTTP request $request = "POST " . $path . " HTTP/1.0" . CRLF . 'Host: ' . $host . CRLF . "Accept-Encoding: gzip" . CRLF . "User-Agent: YACS (www.yacs.fr)" . CRLF . "Connection: close" . CRLF . $headers . CRLF . $data; // save the request if debug mode if ($context['debug_call'] == 'Y') { Logger::remember('services/call.php: Call::list_resources() request', str_replace("\r\n", "\n", $request), 'debug'); } // submit the request fputs($handle, $request); // get everything by Ethernet-sized chunks $response = ''; while (!feof($handle) && strlen($response) < 5242880) { $response .= fread($handle, 1500); } fclose($handle); // ensure we have a valid HTTP status line if (preg_match('/^HTTP/', $response) && !preg_match('/^HTTP\\/[0-9\\.]+ 200 /', $response)) { $lines = explode("\n", $response, 2); return array(FALSE, 'Unexpected HTTP status "' . $lines[0] . '"'); } // separate headers from body list($headers, $content) = explode(CRLF . CRLF, $response, 2); // uncompress payload if necessary if (preg_match('/Content-Encoding: \\s*gzip/i', $headers)) { $content = gzinflate(substr($content, 10)); } // save the response if debug mode if ($context['debug_call'] == 'Y') { Logger::remember('services/call.php: Call::list_resources() response', str_replace("\r\n", "\n", $headers . "\n\n" . $content), 'debug'); } // we understand only text responses if (!preg_match('/^Content-Type: text/m', $headers)) { return array(FALSE, 'Impossible to process not-textual response'); } // passthrough if not xml if (!preg_match('/^Content-Type: text\\/xml/m', $headers)) { return $content; } // select a codec handler include_once $context['path_to_root'] . 'services/codec.php'; include_once $context['path_to_root'] . 'services/rss_codec.php'; $codec = new RSS_Codec(); if (!is_object($codec)) { return array(FALSE, 'Impossible to load codec RSS_Codec'); } // decode the result return $codec->import_response($content, $headers, $parameters); }
/** * scan a file for viruses * * This function connects to ClamAV daemon, if possible, to scan the referred file. * * @param string absolute path of the file to scan * @return string 'Y' if the file has been infected, '?' if clamav is not available, or 'N' if no virus has been found */ public static function has_virus($file) { global $context; // file scanning must be configured if (!isset($context['clamav_check']) || $context['clamav_check'] === 'N') { return 'N'; } // we can't connect to clamav daemon $server = 'localhost'; if (!($handle = Safe::fsockopen($server, 3310, $errno, $errstr, 1))) { if ($context['with_debug'] == 'Y') { Logger::remember('files/files.php: Unable to connect to CLAMAV daemon', '', 'debug'); } return '?'; } // ensure enough execution time Safe::set_time_limit(30); // scan uploaded file $request = 'SCAN ' . $file; fputs($handle, $request . CRLF); if ($context['with_debug'] == 'Y') { Logger::remember('files/files.php: CLAMAV ->', $request, 'debug'); } // expecting an OK if (($reply = fgets($handle)) === FALSE) { Logger::remember('files/files.php: No reply to SCAN command at ' . $server); fclose($handle); return '?'; } if ($context['with_debug'] == 'Y') { Logger::remember('files/files.php: CLAMAV <-', $reply, 'debug'); } // file has been infected! if (!stripos($reply, ': ok')) { Logger::remember('files/files.php: Infected upload by ' . Surfer::get_name()); fclose($handle); return 'Y'; } // everything is ok fclose($handle); return 'N'; }
/** * dynamically generate the page * * @see skins/index.php */ function send_body() { global $context, $local; // $local is required to localize included scripts // include every script that has to be run once global $scripts, $scripts_count; if (@count($scripts)) { // the alphabetical order may be used to control script execution order sort($scripts); reset($scripts); // process each script one by one foreach ($scripts as $item) { // do not execute on first installation if (file_exists('../parameters/switch.on') || file_exists('../parameters/switch.off')) { // ensure we have a valid database resource if (!$context['connection']) { break; } // remember this as an event Logger::remember('scripts/run_once.php: ' . sprintf(i18n::c('script %s has been executed'), $item)); // where scripts actually are $actual_item = str_replace('//', '/', $context['path_to_root'] . 'scripts/run_once/' . $item); // include the script to execute it $scripts_count++; echo Skin::build_block($item, 'subtitle'); include $actual_item; echo "\n"; } // ensure enough overall execution time Safe::set_time_limit(30); // stamp the file to remember execution time Safe::touch($actual_item); // rename the script to avoid further execution Safe::unlink($actual_item . '.done'); Safe::rename($actual_item, $actual_item . '.done'); } // refresh javascript libraries Cache::purge('js'); } // report on actual execution if ($scripts_count) { echo '<p> </p><p>' . sprintf(i18n::ns('%d script has been executed', '%d scripts have been executed', $scripts_count), $scripts_count) . "</p>\n"; } else { echo '<p>' . i18n::s('No script has been executed') . "</p>\n"; } // display the total execution time $time = round(get_micro_time() - $context['start_time'], 2); if ($time > 30) { echo '<p>' . sprintf(i18n::s('Script terminated in %.2f seconds.'), $time) . '</p>'; } // if the server has been switched off, go back to the control panel if (file_exists('../parameters/switch.off')) { echo '<form method="get" action="' . $context['url_to_root'] . 'control/">' . "\n" . '<p class="assistant_bar">' . Skin::build_submit_button(i18n::s('Control Panel')) . '</p>' . "\n" . '</form>' . "\n"; // else back to the control panel as well, but without a button } else { $menu = array('control/' => i18n::s('Control Panel')); echo Skin::build_list($menu, 'menu_bar'); } // purge the cache, since it is likely that we have modified some data Cache::clear(); }
/** * wait for updates * * This script will wait for new updates before providing them to caller. * Because of potential time-outs, you have to care of retries. * * @param string reference to thread (e.g., 'article:123') * @param string timestamp of previous update * @return array attributes including new comments and a timestamp * * @see articles/view_as_chat.php * @see comments/thread.php */ public static function &pull($anchor, $stamp, $count = 100) { global $context; $timer = 1; // some implementations will kill network connections earlier anyway Safe::set_time_limit(max(30, $timer)); // we return formatted text $text = ''; // sanity check if (!$anchor) { return $text; } // the query to get time of last update $query = "SELECT edit_date, edit_name FROM " . SQL::table_name('comments') . " AS comments " . " WHERE comments.anchor LIKE '" . SQL::escape($anchor) . "'" . " ORDER BY comments.edit_date DESC" . " LIMIT 1"; // we may timeout ourself, to be safe with network resources while (!($stat = SQL::query_first($query)) || isset($stat['edit_date']) && $stat['edit_date'] <= $stamp) { // kill the request to avoid repeated transmissions when nothing has changed if (--$timer < 1) { http::no_content(); die; } // preserve server resources sleep(1); } // return an array of variables $response = array(); $response['items'] =& Comments::list_by_thread_for_anchor($anchor, 0, $count, 'thread'); $response['name'] = strip_tags($stat['edit_name']); $response['timestamp'] = SQL::strtotime($stat['edit_date']); // return by reference return $response; }
/** * attempt to use the trackback interface * * @param string some text, extracted from the target site, to extract the broker URL, if any * @param string the source address * @param string the target address from which the text has been extracted * @param string title of the source page * @param string excerpt of the source page * @param string blog name of the source page * @return TRUE if the target site has been pinged back, FALSE otherwise * * @link http://www.movabletype.org/docs/mttrackback.html TrackBack Technical Specification */ public static function ping_as_trackback($text, $source, $target, $title = '', $excerpt = '', $blog_name = '') { global $context; // extract all rdf blocks preg_match_all('/<rdf:RDF(.*)<\\/rdf:RDF>/iUs', $text, $blocks); // nothing to do if (!@count($blocks[1])) { return FALSE; } // look for the broker $broker = array(); foreach ($blocks[1] as $block) { // seek the trackback interface if (!preg_match('/(dc:identifier|about)="' . preg_quote($target, '/') . '/mi', $block)) { continue; } // extract the broker link if (preg_match('/trackback:ping="([^"]+)"/mi', $block, $broker)) { break; } } // trackback interface not supported at this page if (!isset($broker[1])) { return FALSE; } // parse the broker URL $items = @parse_url($broker[1]); // no host, assume it's us if (!($host = $items['host'])) { $host = $context['host_name']; } // no port, assume the standard if (!isset($items['port']) || !($port = $items['port'])) { $port = 80; } // outbound web is not authorized if (isset($context['without_outbound_http']) && $context['without_outbound_http'] == 'Y') { if (isset($context['debug_trackback']) && $context['debug_trackback'] == 'Y') { Logger::remember('links/links.php: Links::ping_as_trackback()', 'Outbound HTTP is not authorized.', 'debug'); } return FALSE; } // connect to the server if (!($handle = Safe::fsockopen($host, $port, $errno, $errstr, 30))) { if (isset($context['debug_trackback']) && $context['debug_trackback'] == 'Y') { Logger::remember('links/links.php: Links::ping_as_trackback()', sprintf('Impossible to connect to %s.', $host . ':' . $port), 'debug'); } return FALSE; } // ensure enough execution time Safe::set_time_limit(30); // build the path, including any query $path = $items['path']; if (isset($items['query']) && $items['query']) { $path .= '?' . $items['query']; } // encode the content $data = 'title=' . urlencode($title) . '&url=' . urlencode($source) . '&excerpt=' . urlencode($excerpt) . '&blog_name=' . urlencode($blog_name); $headers = 'Content-Type: application/x-www-form-urlencoded' . CRLF . 'Content-Length: ' . strlen($data) . CRLF; // actual trackback, through HTTP POST $request = "POST " . $path . " HTTP/1.0" . CRLF . 'Host: ' . $host . CRLF . "User-Agent: YACS (www.yacs.fr)" . CRLF . "Connection: close" . CRLF . $headers . CRLF . $data; // save the request if debug mode if (isset($context['debug_trackback']) && $context['debug_trackback'] == 'Y') { Logger::remember('links/links.php: Links::ping_as_trackback() request', str_replace("\r\n", "\n", $request), 'debug'); } // submit the request fputs($handle, $request); // we are interested only in the very first bytes of the response $code = fread($handle, 15); fclose($handle); // save the response if debug mode if (isset($context['debug_trackback']) && $context['debug_trackback'] == 'Y') { Logger::remember('links/links.php: Links::ping_as_trackback() response', $code . '...', 'debug'); } // check HTTP status if (!preg_match('/^HTTP\\/[0-9\\.]+ 200/', $code)) { return FALSE; } // successful trackback if (isset($context['debug_trackback']) && $context['debug_trackback'] == 'Y') { Logger::remember('links/links.php: Links::ping_as_trackback() success', $broker[1], 'debug'); } return TRUE; }
/** * list running scripts below a certain path * * This script is used to list scripts below the YACS installation path. * Special directories 'scripts/reference' and 'scripts/staging' are skipped. * Also directory entries named either 'files' or 'images' are not recursively scanned, * because of the potential high number of uninteresting files they can contain. * * Also echo '.' (one per file) and '!' (one per directory) during the scan, * if the verbose parameter is set to TRUE. * * @param string the path to scan * @param boolean TRUE to animate the screen, FALSE to stay silent * @return an array of file names * * @see scripts/build.php */ public static function list_scripts_at($path, $verbose = TRUE) { global $context, $script_count; // we want a list of files $files = array(); $path_translated = $context['path_to_root']; if ($path) { $path_translated .= '/' . $path; } if ($handle = Safe::opendir($path_translated)) { while (($node = Safe::readdir($handle)) !== FALSE) { if ($node[0] == '.') { continue; } // avoid listing of special directories if ($node == 'reference' || $node == 'staging') { continue; } // make a real name if ($path) { $target = $path . '/' . $node; } else { $target = $node; } $target_translated = $path_translated . '/' . $node; // scan a sub directory if (is_dir($target_translated)) { // skip files and images, because of so many sub directories if (strpos($path, 'files/') !== FALSE || strpos($path, 'images/') !== FALSE) { continue; } // already included if (strpos($path, 'included/') !== FALSE) { continue; } // extend the list recursively $files = array_merge($files, Scripts::list_scripts_at($target)); // animate the screen if ($verbose) { $context['text'] .= '!'; } if ($script_count++ > 50) { $script_count = 0; if ($verbose) { $context['text'] .= BR . "\n"; } } // scan a file } elseif (preg_match('/\\.php$/i', $node) && is_readable($target_translated)) { // append the script to the list if ($path) { $files[] = $path . '/' . $node; } else { $files[] = $node; } // animate the screen if ($verbose) { $context['text'] .= '.'; } if ($script_count++ > 50) { $script_count = 0; if ($verbose) { $context['text'] .= BR . "\n"; } Safe::set_time_limit(30); } } } Safe::closedir($handle); } return $files; }