390 lines
9.5 KiB
PHP
390 lines
9.5 KiB
PHP
<?php
|
|
|
|
$rpc_internal_timeout = 10;
|
|
$rpc_external_timeout = 4;
|
|
|
|
function rpc_start_request_with_options($url, $options)
|
|
{
|
|
global $rpc_internal_timeout, $rpc_external_timeout;
|
|
|
|
// FIXME: $internal was undefined
|
|
$internal = false;
|
|
|
|
$curlopts = array(
|
|
CURLOPT_FAILONERROR => true, //...?
|
|
CURLOPT_FOLLOWLOCATION => true,
|
|
CURLOPT_RETURNTRANSFER => true,
|
|
|
|
CURLOPT_CONNECTTIMEOUT => 2,
|
|
CURLOPT_TIMEOUT => $internal ? $rpc_internal_timeout : $rpc_external_timeout,
|
|
CURLOPT_USERAGENT => "4chan.org",
|
|
);
|
|
|
|
$curlopts = $options + $curlopts;
|
|
|
|
global $rpc_mh;
|
|
global $rpc_chs;
|
|
|
|
$ch = curl_init($url);
|
|
if (!$ch || !is_resource($ch)) {
|
|
internal_error_log("rpc", "couldn't curl_init '$ch' '$url': '".curl_error($ch)."'");
|
|
curl_close($ch);
|
|
return null;
|
|
}
|
|
|
|
$optstr = print_r($curlopts, TRUE);
|
|
// quick_log_to( "/www/perhost/curls.log", "curl: '$ch' URL: $url\n$optstr\n");
|
|
|
|
foreach ($curlopts as $opt=>$value) {
|
|
if (curl_setopt($ch, $opt, $value) === false) {
|
|
internal_error_log("rpc", "couldn't curl_setopt '$ch' '$opt' '$value': '".curl_error($ch)."'");
|
|
curl_close($ch);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
if (!isset($rpc_mh)) {
|
|
rpc_multi_init();
|
|
}
|
|
|
|
$ret = curl_multi_add_handle($rpc_mh, $ch);
|
|
|
|
if ($ret != 0) {
|
|
internal_error_log("rpc", "couldn't add curl handle $ch $ret: ".curl_error($ch));
|
|
return null;
|
|
}
|
|
|
|
$rpc_chs[] = $ch;
|
|
|
|
return $ch;
|
|
}
|
|
|
|
// returns a request ID, or null if it failed
|
|
function rpc_start_request($url, $post, $cookies, $internal)
|
|
{
|
|
global $rpc_internal_timeout, $rpc_external_timeout;
|
|
|
|
$cookiestr = '';
|
|
if ($cookies) {
|
|
$carray = array();
|
|
foreach($cookies as $name=>$value) {
|
|
$name = urlencode($name);
|
|
$value = urlencode($value);
|
|
$carray[] = "$name=$value";
|
|
}
|
|
$cookiestr = implode("; ", $carray).";";
|
|
}
|
|
|
|
if ($internal) {
|
|
$curlopts[CURLOPT_SSL_VERIFYHOST] = false;
|
|
$curlopts[CURLOPT_SSL_VERIFYPEER] = false;
|
|
}
|
|
else {
|
|
$curlopts[CURLINFO_HEADER_OUT] = true;
|
|
// $curlopts[CURLOPT_VERBOSE] = true;
|
|
}
|
|
|
|
if ($post) {
|
|
$curlopts[CURLOPT_POSTFIELDS] = $post;
|
|
}
|
|
|
|
if ($cookiestr) {
|
|
$curlopts[CURLOPT_COOKIE] = $cookiestr;
|
|
}
|
|
|
|
return rpc_start_request_with_options($url, $curlopts);
|
|
}
|
|
|
|
function rpc_start_captcha_request($url, $post, $cookies, $internal) {
|
|
global $rpc_internal_timeout, $rpc_external_timeout;
|
|
|
|
$cookiestr = '';
|
|
if ($cookies) {
|
|
$carray = array();
|
|
foreach($cookies as $name=>$value) {
|
|
$name = urlencode($name);
|
|
$value = urlencode($value);
|
|
$carray[] = "$name=$value";
|
|
}
|
|
$cookiestr = implode("; ", $carray).";";
|
|
}
|
|
|
|
if ($internal) {
|
|
$curlopts[CURLOPT_SSL_VERIFYHOST] = false;
|
|
$curlopts[CURLOPT_SSL_VERIFYPEER] = false;
|
|
}
|
|
else {
|
|
$curlopts[CURLINFO_HEADER_OUT] = true;
|
|
//$curlopts[CURLOPT_RESOLVE] = array('www.google.com:443:172.217.4.132');
|
|
// $curlopts[CURLOPT_VERBOSE] = true;
|
|
}
|
|
|
|
if ($post) {
|
|
$curlopts[CURLOPT_POSTFIELDS] = $post;
|
|
}
|
|
|
|
if ($cookiestr) {
|
|
$curlopts[CURLOPT_COOKIE] = $cookiestr;
|
|
}
|
|
|
|
return rpc_start_request_with_options($url, $curlopts);
|
|
}
|
|
|
|
function rpc_new_multi_handle()
|
|
{
|
|
$mh = curl_multi_init();
|
|
|
|
curl_multi_setopt($mh, CURLMOPT_PIPELINING, 1);
|
|
curl_multi_setopt($mh, CURLMOPT_MAXCONNECTS, 16);
|
|
|
|
return $mh;
|
|
}
|
|
|
|
function rpc_multi_init()
|
|
{
|
|
global $rpc_mh;
|
|
global $rpc_chs;
|
|
|
|
$rpc_mh = rpc_new_multi_handle();
|
|
$rpc_chs = array();
|
|
|
|
register_shutdown_function('rpc_finish_all');
|
|
}
|
|
|
|
// call at idle points, calls curl's task until it stops having immediate work
|
|
function rpc_task()
|
|
{
|
|
global $rpc_mh;
|
|
|
|
if (!is_resource($rpc_mh)) return false;
|
|
|
|
$still_running = false;
|
|
|
|
do {
|
|
$ret = curl_multi_exec($rpc_mh, $still_running);
|
|
} while ($ret == CURLM_CALL_MULTI_PERFORM);
|
|
|
|
return $still_running;
|
|
}
|
|
|
|
// blocks till all requests are no longer 'running' and clears rpc_mh
|
|
// this can block for a few seconds, watch out!
|
|
function rpc_finish_all()
|
|
{
|
|
global $rpc_mh;
|
|
global $rpc_chs;
|
|
|
|
if (!is_resource($rpc_mh) || !count($rpc_chs)) return;
|
|
|
|
flush_output_buffers();
|
|
|
|
do {
|
|
if (rpc_task() == false) break;
|
|
curl_multi_select($rpc_mh);
|
|
} while (true);
|
|
|
|
// clear out the curl_multi handle
|
|
foreach ($rpc_chs as $ch) {
|
|
curl_multi_remove_handle($rpc_mh, $ch);
|
|
}
|
|
|
|
//quick_log_to("/www/perhost/rpc.log", getmypid()." $n rpcs finished in $rpc_mh\n");
|
|
|
|
//deallocate all curl handles
|
|
$rpc_chs = array();
|
|
|
|
//hopefully rpc_mh is empty now.
|
|
//we don't want to close it because curl uses the state for http pipelining
|
|
}
|
|
|
|
function rpc_close_multi()
|
|
{
|
|
global $rpc_mh;
|
|
global $rpc_chs;
|
|
|
|
// explicitly close these objects since curl debug seems to not print otherwise?
|
|
// don't wait for them to finish. this can be used to prevent double-submits. maybe...
|
|
unset($rpc_chs);
|
|
unset($rpc_mh);
|
|
}
|
|
|
|
function rpc_debug_fd()
|
|
{
|
|
static $fd = -1;
|
|
|
|
if ($fd == -1) {
|
|
$fd = fopen( "/www/perhost/curl-debug.log", "a" );
|
|
fwrite($fd, "--------------\n");
|
|
}
|
|
|
|
return $fd;
|
|
}
|
|
|
|
function rpc_debug_request($ch)
|
|
{
|
|
$errno = curl_errno($ch);
|
|
$error = curl_error($ch);
|
|
$sent = curl_getinfo($ch, CURLINFO_HEADER_OUT);
|
|
$bsent = curl_getinfo($ch, CURLINFO_SIZE_UPLOAD);
|
|
$brec = curl_getinfo($ch, CURLINFO_SIZE_DOWNLOAD);
|
|
|
|
quick_log_to("/www/perhost/rpc-failures.log", getmypid()." curl error $errno '$error'\nbytes up $bsent down $brec\ndata: $sent");
|
|
}
|
|
|
|
// returns the response, or sets $error if null
|
|
// DANGER: if you call this on a curl after rpc_finish_all() it seems to send the POST over again
|
|
function rpc_finish_request($ch, &$error, &$httperror = null)
|
|
{
|
|
rpc_task();
|
|
|
|
// Move the request into the foreground and block (hopefully not actually blocking)
|
|
global $rpc_mh;
|
|
global $rpc_chs;
|
|
|
|
if ($rpc_mh===null || !is_resource($ch)) {
|
|
$error = "Connections not started ($rpc_mh $ch)";
|
|
return null;
|
|
}
|
|
|
|
curl_multi_remove_handle($rpc_mh, $ch);
|
|
$ret = curl_exec($ch);
|
|
|
|
// Get contents
|
|
if ($ret === false) {
|
|
$errstr = curl_error($ch);
|
|
$errno = curl_errno($ch);
|
|
$error = "Curl error: $errstr ($errno)";
|
|
if ($httperror !== null) {
|
|
$httperror = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
|
}
|
|
rpc_debug_request($ch);
|
|
$ret = null;
|
|
}
|
|
|
|
curl_close($ch);
|
|
|
|
if (($pos = array_search($ch, $rpc_chs, true)) !== FALSE) {
|
|
unset($rpc_chs[$pos]);
|
|
}
|
|
|
|
return $ret;
|
|
}
|
|
|
|
// some dumb shit for sending HTTP POST to another server.
|
|
// only use this function internally
|
|
function rpc_send_request($host, $url, $request, &$error, $internal=true) {
|
|
$port = 80;
|
|
$proto = 'tcp://';
|
|
$internal_network = preg_match( '#\.int$#', $host ) || strpos( $host, '10.0' ) === 0;
|
|
|
|
if(strpos($host, "4chan.org") !== false || $internal_network ) {
|
|
if( strpos( $url, 'imgboard.php' ) !== false || strpos( $url, 'admin.php' ) !== false || strpos( $host, 'www.' ) !== false || $internal_network ) {
|
|
$proto = 'ssl://';
|
|
$port = 443;
|
|
}
|
|
}
|
|
|
|
$timeout = $internal_network ? 60 : 4;
|
|
|
|
$cookie = '';
|
|
foreach($request['COOKIE'] as $name=>$value) {
|
|
$name = urlencode($name);
|
|
$value = urlencode($value);
|
|
$cookie .= "$name=$value;";
|
|
}
|
|
|
|
$postbody = '';
|
|
foreach($request['POST'] as $name=>$value) {
|
|
$name = urlencode($name);
|
|
$value = urlencode($value);
|
|
$postbody .= "$name=$value&";
|
|
}
|
|
|
|
// POSTing with HTTP 1.1 tends to make responders send
|
|
// back Transfer-encoding: chunked, so use 1.0
|
|
|
|
$header = "POST $url HTTP/1.0\r\n";
|
|
$header .= "Host: $host\r\n";
|
|
$header .= "Content-Type: application/x-www-form-urlencoded\r\n";
|
|
$header .= "Content-Length: ". strlen($postbody) . "\r\n";
|
|
$header .= "User-Agent: 4chan.org\r\n";
|
|
if ($cookie && $internal) $header .= "Cookie: $cookie\r\n";
|
|
$header .= "Connection: close\r\n";
|
|
$header .= "\r\n";
|
|
|
|
$header .= "$postbody\r\n";
|
|
|
|
$rpc_start_time = microtime(true);
|
|
$socket = fsockopen($proto.$host, $port, $errno, $errstr, $timeout);
|
|
if(!$socket) {
|
|
$error = $errstr; return;
|
|
}
|
|
|
|
if(fwrite($socket, $header) != strlen($header)) {
|
|
$error = 'Could not write to socket'; return;
|
|
}
|
|
|
|
$rpc_connect_time = microtime(true);
|
|
$rpc_connect_took = $rpc_connect_time - $rpc_start_time;
|
|
stream_set_timeout($socket, $timeout - $rpc_connect_took);
|
|
|
|
$response = '';
|
|
do {
|
|
$response .= fgets($socket, 1160);
|
|
$info = stream_get_meta_data($socket);
|
|
} while(!feof($socket) && !$info['timed_out']);
|
|
|
|
fclose($socket);
|
|
if(!preg_match('!^HTTP/1\.. 200 OK!', $response)) {
|
|
$lines = explode("\n", $response);
|
|
$error = 'Error response from server ('.strlen($response).' bytes): '. $lines[0];
|
|
$response = null;
|
|
}
|
|
|
|
// $rpc_end_time = microtime(true);
|
|
// $rpc_took = $rpc_end_time - $rpc_start_time;
|
|
|
|
/*
|
|
if ($error) {
|
|
quick_log_to("/www/perhost/rpc-slow.log", "ERROR: $host$url ct $rpc_connect_took took $rpc_took error: ".implode("\n",$lines)."\n".$postbody);
|
|
} else
|
|
if ($rpc_took > 1) {
|
|
quick_log_to("/www/perhost/rpc-slow.log", "SLOW: $host$url ct $rpc_connect_took took $rpc_took errored ".($response?0:1)."\n".$postbody);
|
|
}
|
|
*/
|
|
|
|
return $response;
|
|
}
|
|
|
|
// a shortcut to create the request object (with cookie set and POST initialized)
|
|
function rpc_blank_request() {
|
|
return array('COOKIE' => $_COOKIE, 'POST' => array() );
|
|
}
|
|
|
|
function rpc_log_url($s,$r) {
|
|
$s = nl2br($s);
|
|
$r = nl2br($r);
|
|
$rh = fopen("/www/perhost/rpc.log", "a");
|
|
flock($rh, LOCK_EX);
|
|
fwrite($rh, "$s --> $r\n");
|
|
fclose($rh);
|
|
}
|
|
|
|
// TODO: This isn't really used since we just block link shorteners instead.
|
|
// But it should be optimized into curl_multi if possible.
|
|
function rpc_find_real_url($short) {
|
|
$cu = curl_init($short);
|
|
curl_setopt($cu, CURLOPT_FOLLOWLOCATION, true);
|
|
curl_setopt($cu, CURLOPT_MAXREDIRS, 4);
|
|
curl_setopt($cu, CURLOPT_NOBODY, true);
|
|
curl_setopt($cu, CURLOPT_TIMEOUT, 10);
|
|
curl_setopt($cu, CURLOPT_USERAGENT, "4chan.org");
|
|
|
|
if (curl_exec($cu)) {
|
|
$ret = curl_getinfo($cu, CURLINFO_EFFECTIVE_URL);
|
|
} else $ret = FALSE;
|
|
|
|
curl_close($cu);
|
|
//rpc_log_url($short,$ret);
|
|
return $ret;
|
|
}
|