pingback.php 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. <?php
  2. /*
  3. Copyright (c) 2009-2014 F3::Factory/Bong Cosca, All rights reserved.
  4. This file is part of the Fat-Free Framework (http://fatfree.sf.net).
  5. THE SOFTWARE AND DOCUMENTATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF
  6. ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
  7. IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
  8. PURPOSE.
  9. Please see the license.txt file for more information.
  10. */
  11. namespace Web;
  12. //! Pingback 1.0 protocol (client and server) implementation
  13. class Pingback extends \Prefab {
  14. protected
  15. //! Transaction history
  16. $log;
  17. /**
  18. * Return TRUE if URL points to a pingback-enabled resource
  19. * @return bool
  20. * @param $url
  21. **/
  22. protected function enabled($url) {
  23. $web=\Web::instance();
  24. $req=$web->request($url);
  25. $found=FALSE;
  26. if ($req && $req['body']) {
  27. // Look for pingback header
  28. foreach ($req['headers'] as $header)
  29. if (preg_match('/^X-Pingback:\h*(.+)/',$header,$href)) {
  30. $found=$href[1];
  31. break;
  32. }
  33. if (!$found &&
  34. // Scan page for pingback link tag
  35. preg_match('/<link\h+(.+?)\h*\/?>/i',$req['body'],$parts) &&
  36. preg_match('/rel\h*=\h*"pingback"/i',$parts[1]) &&
  37. preg_match('/href\h*=\h*"\h*(.+?)\h*"/i',$parts[1],$href))
  38. $found=$href[1];
  39. }
  40. return $found;
  41. }
  42. /**
  43. * Load local page contents, parse HTML anchor tags, find permalinks,
  44. * and send XML-RPC calls to corresponding pingback servers
  45. * @return NULL
  46. * @param $source string
  47. **/
  48. function inspect($source) {
  49. $fw=\Base::instance();
  50. $web=\Web::instance();
  51. $parts=parse_url($source);
  52. if (empty($parts['scheme']) || empty($parts['host']) ||
  53. $parts['host']==$fw->get('HOST')) {
  54. $req=$web->request($source);
  55. $doc=new \DOMDocument('1.0',$fw->get('ENCODING'));
  56. $doc->stricterrorchecking=FALSE;
  57. $doc->recover=TRUE;
  58. if ($req && @$doc->loadhtml($req['body'])) {
  59. // Parse anchor tags
  60. $links=$doc->getelementsbytagname('a');
  61. foreach ($links as $link) {
  62. $permalink=$link->getattribute('href');
  63. // Find pingback-enabled resources
  64. if ($permalink && $found=$this->enabled($permalink)) {
  65. $req=$web->request($found,
  66. array(
  67. 'method'=>'POST',
  68. 'header'=>'Content-Type: application/xml',
  69. 'content'=>xmlrpc_encode_request(
  70. 'pingback.ping',
  71. array($source,$permalink),
  72. array('encoding'=>$fw->get('ENCODING'))
  73. )
  74. )
  75. );
  76. if ($req && $req['body'])
  77. $this->log.=date('r').' '.
  78. $permalink.' [permalink:'.$found.']'.PHP_EOL.
  79. $req['body'].PHP_EOL;
  80. }
  81. }
  82. }
  83. unset($doc);
  84. }
  85. }
  86. /**
  87. * Receive ping, check if local page is pingback-enabled, verify
  88. * source contents, and return XML-RPC response
  89. * @return string
  90. * @param $func callback
  91. * @param $path string
  92. **/
  93. function listen($func,$path=NULL) {
  94. $fw=\Base::instance();
  95. if (PHP_SAPI!='cli') {
  96. header('X-Powered-By: '.$fw->get('PACKAGE'));
  97. header('Content-Type: application/xml; '.
  98. 'charset='.$charset=$fw->get('ENCODING'));
  99. }
  100. if (!$path)
  101. $path=$fw->get('BASE');
  102. $web=\Web::instance();
  103. $args=xmlrpc_decode_request($fw->get('BODY'),$method,$charset);
  104. $options=array('encoding'=>$charset);
  105. if ($method=='pingback.ping' && isset($args[0],$args[1])) {
  106. list($source,$permalink)=$args;
  107. $doc=new \DOMDocument('1.0',$fw->get('ENCODING'));
  108. // Check local page if pingback-enabled
  109. $parts=parse_url($permalink);
  110. if ((empty($parts['scheme']) ||
  111. $parts['host']==$fw->get('HOST')) &&
  112. preg_match('/^'.preg_quote($path,'/').'/'.
  113. ($fw->get('CASELESS')?'i':''),$parts['path']) &&
  114. $this->enabled($permalink)) {
  115. // Check source
  116. $parts=parse_url($source);
  117. if ((empty($parts['scheme']) ||
  118. $parts['host']==$fw->get('HOST')) &&
  119. ($req=$web->request($source)) &&
  120. $doc->loadhtml($req['body'])) {
  121. $links=$doc->getelementsbytagname('a');
  122. foreach ($links as $link) {
  123. if ($link->getattribute('href')==$permalink) {
  124. call_user_func_array($func,
  125. array($source,$req['body']));
  126. // Success
  127. die(xmlrpc_encode_request(NULL,$source,$options));
  128. }
  129. }
  130. // No link to local page
  131. die(xmlrpc_encode_request(NULL,0x11,$options));
  132. }
  133. // Source failure
  134. die(xmlrpc_encode_request(NULL,0x10,$options));
  135. }
  136. // Doesn't exist (or not pingback-enabled)
  137. die(xmlrpc_encode_request(NULL,0x21,$options));
  138. }
  139. // Access denied
  140. die(xmlrpc_encode_request(NULL,0x31,$options));
  141. }
  142. /**
  143. * Return transaction history
  144. * @return string
  145. **/
  146. function log() {
  147. return $this->log;
  148. }
  149. /**
  150. * Instantiate class
  151. * @return object
  152. **/
  153. function __construct() {
  154. // Suppress errors caused by invalid HTML structures
  155. libxml_use_internal_errors(TRUE);
  156. }
  157. }