*
* Receive Pingbacks and Trackbacks
*
* Developed and tested using PmWiki 2.2.x
*
* To use, add the following to a configuration file:
include_once("$FarmD/cookbook/bloge-linkback-server.php");
* To limit receiving linkback pings you should only include this file
* on pages which will accept linkbacks, for example by using per-group
* customizations.
*
* This is a part of the Bloge bundle of recipes, but may be used by itself.
* For more information, please see the online documentation at
* http://www.pmwiki.org/wiki/Cookbook/Bloge and at
* http://www.pmwiki.org/wiki/Cookbook/Bloge-Linkback#server
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
$RecipeInfo['Bloge-LinkbackServer']['Version'] = '2009-08-12';
$HTMLHeaderFmt['linkback'] = 'function:LinkbackHeader';
function LinkbackHeader($pagename) {
global $LinkbackHeader;
SDVA($LinkbackHeader, array( 'pingback' => 1, 'trackback' => 1 ));
$url = PageVar($pagename,'$PageUrl');
if (!empty($LinkbackHeader['pingback']))
echo "\n";
## NOTE: this is not fully correct, but is much shorter and will be
## recognised by at least Drupal, Movable Type & Pligg
## See also:
if (!empty($LinkbackHeader['trackback']))
echo "\n";
}
if (($action!='pingback') && ($action!='trackback')) return;
SDV($LinkbackPage, 'Site.Linkbacks');
SDV($HandleActions['pingback'], 'HandleLinkback');
SDV($HandleAuth['pingback'], 'read');
SDV($HandleActions['trackback'], 'HandleLinkback');
SDV($HandleAuth['trackback'], 'read');
if (!function_exists('xmlrpc_decode_request')) {
function xmlrpc_decode_request($xml, &$method, $encoding='iso-8859-1') {
if (!preg_match('#\s*(.*?)\s*#s',$xml,$m_mcall)) return FALSE;
if (!preg_match('#\s*([\w.:/]*?)\s*#',$m_mcall[1],$m_name)) return FALSE;
$method = $m_name[1];
if (!preg_match('#\s*(.*?)\s*#s',$m_mcall[1],$m_params)) return FALSE;
if (!preg_match_all('#\s*\s*<(\w+)>\s*(.*?)\s*\1>\s*\s*#s', $m_params[1], $m_values, PREG_SET_ORDER)) return FALSE;
$out = array();
foreach( $m_values as $va ) $out[] = $va[2];
return $out;
}
}
function LinkbackVerifySource($url, $self, &$errstr) {
global $LinkbackServerCURLOptions;
if ( !function_exists('curl_init') || empty($self) ) return -32400;
if (empty($url)) return 16;
$host = preg_replace('!^[a-z]+://([^/]+).*$!i', '$1', $url);
if (gethostbyname($host) != $_SERVER['REMOTE_ADDR']) { $errstr='source IP mismatch'; return 49; }
SDV($LinkbackServerCURLOptions, array(
CURLOPT_FOLLOWLOCATION => TRUE,
CURLOPT_MAXREDIRS => 10,
CURLOPT_CONNECTTIMEOUT => 10,
CURLOPT_RETURNTRANSFER => TRUE,
CURLOPT_USERAGENT => "$Version Bloge-LinkbackServer-{$RecipeInfo['Bloge-LinkbackServer']['Version']}"
));
## HEAD url
$ch = curl_init($url);
curl_setopt_array($ch, $LinkbackServerCURLOptions + array(
CURLOPT_NOBODY => TRUE,
CURLOPT_HEADER => TRUE,
CURLOPT_REFERER => $url
));
$reply = curl_exec($ch);
if (empty($reply)) $errstr = curl_error($ch);
curl_close($ch);
if (empty($reply)) return 16;
else if (!preg_match('/^Content-Type: .*(html|xml)/mi', $reply, $m)) return 17;
## GET url
$ch = curl_init($url);
curl_setopt_array($ch, $LinkbackServerCURLOptions + array( CURLOPT_REFERER => $url ));
$reply = curl_exec($ch);
if (empty($reply)) $errstr = curl_error($ch);
curl_close($ch);
if (empty($reply)) return 16;
if (!preg_match('#]*\bhref=([\'"])'.preg_quote($self,'#').'\1[^>]*>\s*\S+\s*#i', $reply)) return 17;
return 0;
}
function LinkbackBlocklist($text) {
global
$EnableBlocklist, $FarmD, $EnablePost, $EditFunctions,
$EnableLinkbackBlocklist, $LinkbackPage;
if (!IsEnabled($EnableLinkbackBlocklist,0)) return FALSE;
$EnableBlocklist = $EnableLinkbackBlocklist;
include_once("$FarmD/scripts/blocklist.php");
$EnablePost &= 1;
BlockList($LinkbackPage, $text);
if (!$EnablePost) return TRUE;
$cbkey = array_search('CheckBlocklist', $EditFunctions);
if ($cbkey !== FALSE) unset($EditFunctions[$cbkey]);
return FALSE;
}
function LinkbackExecute($pagename, $auth, &$type, &$errstr) {
global
$action, $HTTP_RAW_POST_DATA, $Author, $AuthorLink, $IsPagePosted, $CurrentTime,
$LinkbackPage, $LinkbackMarkup;
$tgtpage = RetrieveAuthPage($pagename, 'read', FALSE, READPAGE_CURRENT);
if (!$tgtpage) { $errstr = 'page read error'; return -32500; }
$tgt = PageVar($pagename,'$PageUrl');
$lbpage = RetrieveAuthPage($LinkbackPage, $auth, FALSE);
if (!$lbpage) { $errstr = 'page read error'; return -32500; }
if (empty($_REQUEST['url'])) {
if ($action=='trackback') {
$type = 'trackback';
$errstr = 'no url parameter';
return -32700;
}
$type = 'pingback';
$params = xmlrpc_decode_request($HTTP_RAW_POST_DATA, &$method);
if ( $params === FALSE ) return -32600;
if ( $method != 'pingback.ping' ) return -32601;
if ( count($params) != 2 ) return -32602;
if ( $params[1] != $tgt ) { $errstr = 'targetURI doesn\'t match Pingback URI'; return 33; }
$source = $params[0];
} else {
$type = 'trackback';
$source = $_REQUEST['url'];
}
$sn = str_replace('.','--',$pagename);
$lbsection = TextSection($lbpage['text'], "#$sn#");
if (preg_match('#\s'.preg_quote($source,'#').'\s#', $lbsection)) return 48;
if (LinkbackBlocklist($source)) { $errstr='blocked'; return 49; }
$source_code = LinkbackVerifySource($source, $tgt, $errstr);
if ($source_code) return $source_code;
SDVA($LinkbackMarkup, array(
'head' => "\$pageanchor\n!!! Linkbacks",
'item' => "\n* \$source ($CurrentTime)"
));
$lbnew = $lbpage;
foreach ( array('head','item') as $t ) $lbmarkup[$t] = str_replace(
array('$pagename', '$pageanchor', '$source'),
array( $pagename, "[[#$sn]]", $source),
$LinkbackMarkup[$t]);
if ($lbsection) $lbnew['text'] = str_replace($lbmarkup['head'], $lbmarkup['head'].$lbmarkup['item'], $lbnew['text']);
else $lbnew['text'] .= "\n{$lbmarkup['head']}{$lbmarkup['item']}\n";
$AuthorLink = $Author = "linkback from {$_SERVER['REMOTE_ADDR']}";
UpdatePage($LinkbackPage, $lbpage, $lbnew);
if (!$IsPagePosted) { $errstr = 'page write error'; return -32500; }
return 0;
}
function HandleLinkback($pagename, $auth='read') {
$errstr = '';
Lock(2);
$errcode = LinkbackExecute($pagename, $auth, $type, $errstr);
Lock(0);
switch($errcode) {
//case 0: $str = 'generic fault code'; break;
case 16: $str = 'The source URI does not exist.'; break;
case 17: $str = 'The source URI does not contain a link to the target URI, and so cannot be used as a source.'; break;
case 32: $str = 'The specified target URI does not exist.'; break;
case 33: $str = 'The specified target URI cannot be used as a target.'; break;
case 48: $str = 'The linkback has already been registered.'; break;
case 49: $str = 'Access denied.'; break;
//case 50: $str = 'The server could not communicate with an upstream server, or received an error from an upstream server, and therefore could not complete the request.'; break;
case -32700: $str = 'parse error. not well formed'; break;
//case -32701: $str = 'parse error. unsupported encoding'; break;
//case -32702: $str = 'parse error. invalid character for encoding'; break;
case -32600: $str = 'server error. invalid xml-rpc. not conforming to spec.'; break;
case -32601: $str = 'server error. requested method not found'; break;
case -32602: $str = 'server error. invalid method parameters'; break;
//case -32603: $str = 'server error. internal xml-rpc error'; break;
case -32500: $str = 'application error'; break;
case -32400: $str = 'system error'; break;
//case -32300: $str = 'transport error'; break;
default: $str = 'ok';
}
if ($errstr) $str .= " ($errstr)";
if ($type=='trackback') {
$response = "\n"
. ( $errcode
? "1\n$str [$errcode]"
: "0" )
. "\n";
} else {
## default to pingback
$response = "\n"
. ( $errcode
? "\nfaultCode$errcode\nfaultString$str\n"
: "$str" )
. "\n";
}
header('Content-type: text/xml');
echo "\n$response\n";
exit();
}