| 18 | | class TrackbackAction extends AbstractAction { |
| 19 | | // post data |
| 20 | | protected $postID = 0; |
| 21 | | protected $post; |
| 22 | | |
| 23 | | // foreign data |
| 24 | | protected $title, $excerpt, $url, $blog_name; |
| 25 | | |
| 26 | | /** |
| 27 | | * @see Action::readParameters() |
| 28 | | */ |
| 29 | | public function readParameters() { |
| 30 | | parent::readParameters(); |
| 31 | | |
| 32 | | // post data |
| 33 | | if (isset($_GET['postID'])) $this->postID = intval($_GET['postID']); |
| 34 | | |
| 35 | | // convert everything to our charset |
| 36 | | $charset = isset($_REQUEST['charset']) ? $_REQUEST['charset'] : CHARSET; |
| 37 | | |
| 38 | | // foreign data |
| 39 | | foreach(array('title','excerpt','url','blog_name') as $key) { |
| 40 | | if (isset($_REQUEST[$key])) { |
| 41 | | $this->$key = StringUtil::convertEncoding($_REQUEST['charset'], CHARSET, $_REQUEST[$key]); |
| 42 | | $this->$key = strip_tags($this->$key); |
| 43 | | } |
| 44 | | } |
| 45 | | |
| 46 | | // post details |
| 47 | | $this->post = new Post($this->postID); |
| 48 | | |
| 49 | | } |
| 50 | | |
| 51 | | /** |
| 52 | | * return to sender |
| 53 | | * @param error |
| 54 | | * @param error_message |
| 55 | | */ |
| 56 | | protected function response($error = 0, $error_message = '') { |
| 57 | | header('Content-Type: text/xml; charset='.CHARSET); |
| 58 | | if ($error) { |
| 59 | | echo '<?xml version="1.0" encoding="utf-8"?'.">\n"; |
| 60 | | echo "<response>\n"; |
| 61 | | echo "<error>1</error>\n"; |
| 62 | | echo "<message>$error_message</message>\n"; |
| 63 | | echo "</response>"; |
| 64 | | exit; |
| 65 | | } else { |
| 66 | | echo '<?xml version="1.0" encoding="utf-8"?'.">\n"; |
| 67 | | echo "<response>\n"; |
| 68 | | echo "<error>0</error>\n"; |
| 69 | | echo "</response>"; |
| 70 | | } |
| 71 | | } |
| 72 | | |
| | 12 | class TrackbackUtil { |
| | 13 | protected $agent = 'Woltlab Burning Board Trackback Mod'; |
| | 14 | |
| | 15 | /** |
| | 16 | * fetchs necessery postdata |
| | 17 | * @param postIDs |
| | 18 | * @return array |
| | 19 | */ |
| | 20 | public function getPost($postIDs) { |
| | 21 | if(!is_array($postIDs)) |
| | 22 | $postIDs = array($postIDs); |
| | 23 | |
| | 24 | // fetch thread data |
| | 25 | $sql = "SELECT thread.* |
| | 26 | FROM wbb".WBB_N."_post post |
| | 27 | LEFT JOIN wbb".WBB_N."_thread thread |
| | 28 | ON (thread.threadID=post.threadID) |
| | 29 | WHERE postID IN (".implode(',', $postIDs).") "; |
| | 30 | |
| | 31 | $row = WCF::getDB()->getFirstRow($sql); |
| | 32 | |
| | 33 | $rewriter = new PublicSEORewriter(); |
| | 34 | |
| | 35 | // public seo rewriter: cache |
| | 36 | $rewriter->publicCacheThreads($row['threadID'], $row); |
| | 37 | |
| | 38 | // public seo rewriter use |
| | 39 | $row['url'] = $rewriter->publicParseThreadURLs($row['threadID'], ''); |
| | 40 | |
| | 41 | return $row; |
| | 42 | } |
| | 43 | |
| | 44 | /** |
| | 45 | * saves incoming ping/trackback |
| | 46 | * @param postID |
| | 47 | * @param postURL |
| | 48 | * @param title |
| | 49 | * @param excerpt |
| | 50 | * @param url |
| | 51 | * @param blog_name |
| | 52 | */ |
| | 53 | public function save($postID, $postURL, $title, $excerpt, $url, $blog_name) { |
| | 54 | // akismet check spam |
| | 55 | $isSpam = $this->isSpam($title, $excerpt, $url, $blog_name); |
| | 56 | |
| | 57 | // insert trackback data |
| | 58 | $sql = "INSERT INTO wbb".WBB_N."_trackback |
| | 59 | ( |
| | 60 | postID, |
| | 61 | postURL, |
| | 62 | title, |
| | 63 | excerpt, |
| | 64 | url, |
| | 65 | blog_name, |
| | 66 | isSpam |
| | 67 | ) VALUES ( |
| | 68 | {$postID}, |
| | 69 | '".escapeString($postURL)."', |
| | 70 | '".escapeString($title)."', |
| | 71 | '".escapeString($excerpt)."', |
| | 72 | '".escapeString($url)."', |
| | 73 | '".escapeString($blog_name)."', |
| | 74 | $isSpam} |
| | 75 | );"; |
| | 76 | |
| | 77 | WBBCore::getDB()->sendQuery($sql); |
| | 78 | } |
| | 79 | |
| | 80 | /** |
| | 81 | * does track and ping operations on alien urls |
| | 82 | * @param postID |
| | 83 | * @param alienurl |
| | 84 | * @param page_title |
| | 85 | * @param author |
| | 86 | * @param url |
| | 87 | * @param title |
| | 88 | * @param excerpt |
| | 89 | */ |
| | 90 | public function trackAndPing($postID, $alienurl, $page_title, $author, $url, $title, $excerpt) { |
| | 91 | $content = ""; // hold as reference |
| | 92 | |
| | 93 | // auto discovery |
| | 94 | $pingbacks = $this->discover_pingback_uri($alienurl, $content); |
| | 95 | $trackbacks = count($pingbacks) > 0 ? array() : $this->discover_trackback_uri($content); |
| | 96 | |
| | 97 | // merge pingbacks with public pingback services |
| | 98 | $pingbacks = array_merge($pingbacks, explode("\n", StringUtil::unifyNewlines(MESSAGE_PINGBACK))); |
| | 99 | |
| | 100 | // send pingbacks |
| | 101 | foreach($pingbacks as $pingbackurl) { |
| | 102 | $this->send_pingback($alienurl, $pingbackurl, $page_title, $url); |
| | 103 | $this->save_log($postID, $alienurl); |
| | 104 | } |
| | 105 | |
| | 106 | // send trackbacks |
| | 107 | foreach($trackbacks as $trackbackurl) { |
| | 108 | $this->send_trackback($trackbackurl, $page_title, $author, $url, $title, $excerpt); |
| | 109 | $this->save_log($postID, $alienurl); |
| | 110 | } |
| | 111 | } |
| | 112 | |
| 98 | | |
| 99 | | /** |
| 100 | | * @see Action::execute() |
| 101 | | */ |
| 102 | | public function execute() { |
| 103 | | parent::execute(); |
| 104 | | |
| 105 | | // Set page header to XML |
| 106 | | header('Content-Type: text/xml'); |
| 107 | | |
| 108 | | // fetch data |
| 109 | | $row = TrackbackUtil::getPost($this->postID); |
| 110 | | |
| 111 | | // check if post exists |
| 112 | | if (!$this->post->postID) { |
| 113 | | $this->response(1, 'Sorry, this item does not exist.'); |
| 114 | | } |
| 115 | | // ... and trackback is enabled |
| 116 | | if(!$this->post->hasTrackback) { |
| 117 | | $this->response(1, 'Sorry, trackbacks are closed for this item.'); |
| 118 | | } |
| 119 | | |
| 120 | | // check for duplicates |
| 121 | | $sql = "SELECT COUNT(*) AS c |
| 122 | | FROM wbb".WBB_N."_trackback |
| 123 | | WHERE postID = {$this->postID} |
| 124 | | AND url = '".escapeString($this->url)."' "; |
| 125 | | |
| 126 | | $row = WBBCore::getDB()->getFirstRow($sql); |
| 127 | | if(intval($row['c']) > 0) { |
| 128 | | $this->response(1, 'We already have a ping from that URL for this post.'); |
| 129 | | } |
| 130 | | |
| 131 | | // save |
| 132 | | TrackbackUtil::save($this->postID, $row['url'], $this->title, $this->excerpt, $this->url, $this->blog_name); |
| 133 | | |
| 134 | | // finish |
| 135 | | $this->executed(); |
| 136 | | $this->response(0); |
| 137 | | } |
| 138 | | } |
| | 138 | |
| | 139 | /** |
| | 140 | * discover pingback uri |
| | 141 | * @param url |
| | 142 | * @param contents (reference) |
| | 143 | * @return pingbacks |
| | 144 | */ |
| | 145 | protected function discover_pingback_uri($url, &$contents) { |
| | 146 | $pingbacks = array(); // return var |
| | 147 | |
| | 148 | $byte_count = 0; |
| | 149 | $headers = ''; |
| | 150 | $pingback_str_dquote = 'rel="pingback"'; |
| | 151 | $pingback_str_squote = 'rel=\'pingback\''; |
| | 152 | $x_pingback_str = 'x-pingback: '; |
| | 153 | $pingback_href_original_pos = 27; |
| | 154 | |
| | 155 | // parse url |
| | 156 | $parse = parse_url($url); |
| | 157 | $host = (isset($parse['host'])) ? $parse['host'] : null; |
| | 158 | $path = (isset($parse['path'])) ? $parse['path'] : '/'; |
| | 159 | $path .= (isset($parse['query'])) ? "?".$parse['query'] : ''; |
| | 160 | $port = (isset($parse['port'])) ? $parse['port'] : 80; |
| | 161 | |
| | 162 | // Try to connect to the server at $host |
| | 163 | $fp = @fsockopen($host, $port, $errno, $errstr, 2); |
| | 164 | if ( !$fp ) // Couldn't open a connection to $host |
| | 165 | return false; |
| | 166 | |
| | 167 | // Send the GET request |
| | 168 | $request = "GET {$path} HTTP/1.1\r\nHost: $host\r\nUser-Agent: {$this->agent} \r\n\r\n"; |
| | 169 | fputs($fp, $request); |
| | 170 | |
| | 171 | // Let's check for an X-Pingback header first |
| | 172 | while ( !feof($fp) ) { |
| | 173 | $line = fgets($fp, 512); |
| | 174 | if(trim($line) == '') //empty line = headers complete |
| | 175 | break; |
| | 176 | |
| | 177 | $headers .= trim($line)."\n"; |
| | 178 | $x_pingback_header_offset = strpos(strtolower($headers), $x_pingback_str); |
| | 179 | |
| | 180 | if($x_pingback_header_offset) { |
| | 181 | // We got it! |
| | 182 | preg_match('#x-pingback: (.+)#is', $headers, $matches); |
| | 183 | $pingbacks[] = trim($matches[1]); |
| | 184 | } |
| | 185 | |
| | 186 | if(strpos(strtolower($headers), 'content-type: ')) { |
| | 187 | preg_match('#content-type: (.+)#is', $headers, $matches); |
| | 188 | $content_type = trim($matches[1]); |
| | 189 | } |
| | 190 | } |
| | 191 | |
| | 192 | if ( preg_match('#(image|audio|video|model)/#is', $content_type) ) // Not an (x)html, sgml, or xml page, no use going further |
| | 193 | return false; |
| | 194 | |
| | 195 | while ( !feof($fp) ) { |
| | 196 | $line = fgets($fp, 1024); |
| | 197 | $contents .= trim($line); |
| | 198 | $pingback_link_offset_dquote = strpos($contents, $pingback_str_dquote); |
| | 199 | $pingback_link_offset_squote = strpos($contents, $pingback_str_squote); |
| | 200 | if($pingback_link_offset_dquote || $pingback_link_offset_squote) { |
| | 201 | $quote = ($pingback_link_offset_dquote) ? '"' : '\''; |
| | 202 | $pingback_link_offset = ($quote=='"') ? $pingback_link_offset_dquote : $pingback_link_offset_squote; |
| | 203 | $pingback_href_pos = @strpos($contents, 'href=', $pingback_link_offset); |
| | 204 | $pingback_href_start = $pingback_href_pos+6; |
| | 205 | $pingback_href_end = @strpos($contents, $quote, $pingback_href_start); |
| | 206 | $pingback_server_url_len = $pingback_href_end - $pingback_href_start; |
| | 207 | $pingback_server_url = substr($contents, $pingback_href_start, $pingback_server_url_len); |
| | 208 | // We may find rel="pingback" but an incomplete pingback URL |
| | 209 | if ( $pingback_server_url_len > 0 ) { |
| | 210 | // We got it! |
| | 211 | $pingbacks[] = $pingback_server_url; |
| | 212 | } |
| | 213 | } |
| | 214 | $byte_count += strlen($line); |
| | 215 | if ( $byte_count > $timeout_bytes ) { |
| | 216 | // It's no use going further, there probably isn't any pingback |
| | 217 | // server to find in this file. (Prevents loading large files.) |
| | 218 | break; |
| | 219 | } |
| | 220 | } |
| | 221 | |
| | 222 | return $pingbacks; |
| | 223 | } |
| | 224 | |
| | 225 | /** |
| | 226 | * discover trackback uri |
| | 227 | * @param contents |
| | 228 | * @return trackbacks |
| | 229 | */ |
| | 230 | protected function discover_trackback_uri($contents) { |
| | 231 | $rdf = array(); // <- holds list of RDF segments |
| | 232 | |
| | 233 | if ($contents) { |
| | 234 | preg_match_all('/(<rdf:RDF.*?<\/rdf:RDF>)/sm', $contents, $link_rdf, PREG_SET_ORDER); |
| | 235 | |
| | 236 | // Loop through all rdf segments |
| | 237 | for ($i = 0; $i < count($link_rdf); $i++) { |
| | 238 | if (preg_match('|dc:identifier="' . preg_quote($link) . '"|ms', $link_rdf[$i][1])) { |
| | 239 | $rdf[] = trim($link_rdf[$i][1]); |
| | 240 | } |
| | 241 | } |
| | 242 | } |
| | 243 | |
| | 244 | // Loop through the RDFs array and extract trackback URIs |
| | 245 | $trackbacks = array(); // <- holds list of trackback URIs |
| | 246 | foreach($rdf as $rdf_url) { |
| | 247 | if (preg_match('/trackback:ping="([^"]+)"/', $rdf_url, $array)) { |
| | 248 | $trackbacks[] = trim($array[1]); |
| | 249 | } |
| | 250 | } |
| | 251 | |
| | 252 | return $trackbacks; |
| | 253 | } |
| | 254 | |
| | 255 | |
| | 256 | /** |
| | 257 | * Send a Pingback |
| | 258 | * @param alienurl -> the main url from the destination site |
| | 259 | * @param pingbackurl -> the pingback url from the destination site |
| | 260 | * @param url -> the own url |
| | 261 | */ |
| | 262 | protected function send_pingback($alienurl, $pingbackurl, $url) { |
| | 263 | require_once(WBB_DIR.'lib/util/IXR.class.php'); |
| | 264 | |
| | 265 | // using a timeout of 3 seconds should be enough to cover slow servers |
| | 266 | $client = new IXR_Client($pingbackurl); |
| | 267 | $client->timeout = 3; |
| | 268 | $client->useragent = $this->agent; |
| | 269 | |
| | 270 | // when set to true, this outputs debug messages by itself |
| | 271 | $client->debug = false; |
| | 272 | |
| | 273 | // Already registered |
| | 274 | if ($client->query('pingback.ping', $url, $alienurl) || (isset($client->error->code) && 48 == $client->error->code)) { |
| | 275 | add_ping($post_ID, $pagelinkedto); |
| | 276 | } |
| | 277 | } |
| | 278 | |
| | 279 | /** |
| | 280 | * Send a Trackback |
| | 281 | * @param trackbackurl |
| | 282 | * @param page_title |
| | 283 | * @param author |
| | 284 | * @param url |
| | 285 | * @param title |
| | 286 | * @param excerpt |
| | 287 | */ |
| | 288 | protected function send_trackback($trackbackurl, $page_title, $author, $url, $title, $excerpt) { |
| | 289 | $blog_name = urlencode($page_title); |
| | 290 | $author = urlencode($author); |
| | 291 | $title = urlencode($title); |
| | 292 | $excerpt = urlencode($excerpt); |
| | 293 | |
| | 294 | $query_string = "title=$title&url=$url&blog_name=$blog_name&excerpt=$excerpt"; |
| | 295 | |
| | 296 | $parse = parse_url($trackback_url); |
| | 297 | $http_request = 'POST ' . $parse['path'] . ($parse['query'] ? '?'.$parse['query'] : '') . " HTTP/1.0\r\n"; |
| | 298 | $http_request .= 'Host: '.$parse['host']."\r\n"; |
| | 299 | $http_request .= 'Content-Type: application/x-www-form-urlencoded; charset='.CHARSET."\r\n"; |
| | 300 | $http_request .= 'Content-Length: '.strlen($query_string)."\r\n"; |
| | 301 | $http_request .= "User-Agent: {$this->agent}"; |
| | 302 | $http_request .= "\r\n\r\n"; |
| | 303 | $http_request .= $query_string; |
| | 304 | |
| | 305 | $fs = @fsockopen($parse['host'], isset($parse['port'])?$parse['port']:80, $errno, $errstr, 4); |
| | 306 | @fputs($fs, $http_request); |
| | 307 | @fclose($fs); |
| | 308 | } |
| | 309 | |
| | 310 | /** |
| | 311 | * saves log |
| | 312 | * @param postID |
| | 313 | * @param alienURL |
| | 314 | */ |
| | 315 | protected function save_log($postID, $alienurl) { |
| | 316 | $sql = "INSERT INTO wbb".WBB_N."_trackbackLog |
| | 317 | ( |
| | 318 | postID, |
| | 319 | alienURL, |
| | 320 | timestamp |
| | 321 | ) VALUES ( |
| | 322 | {$postID}, |
| | 323 | '".escapeString($alienurl)."', |
| | 324 | ".time()." |
| | 325 | );"; |
| | 326 | |
| | 327 | WBBCore::getDB()->sendQuery($sql); |
| | 328 | } |
| | 329 | } |