1<?php 2/** 3 * CodeIgniter 4 * 5 * An open source application development framework for PHP 6 * 7 * This content is released under the MIT License (MIT) 8 * 9 * Copyright (c) 2014 - 2018, British Columbia Institute of Technology 10 * 11 * Permission is hereby granted, free of charge, to any person obtaining a copy 12 * of this software and associated documentation files (the "Software"), to deal 13 * in the Software without restriction, including without limitation the rights 14 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 * copies of the Software, and to permit persons to whom the Software is 16 * furnished to do so, subject to the following conditions: 17 * 18 * The above copyright notice and this permission notice shall be included in 19 * all copies or substantial portions of the Software. 20 * 21 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 27 * THE SOFTWARE. 28 * 29 * @package CodeIgniter 30 * @author EllisLab Dev Team 31 * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/) 32 * @copyright Copyright (c) 2014 - 2018, British Columbia Institute of Technology (http://bcit.ca/) 33 * @license http://opensource.org/licenses/MIT MIT License 34 * @link https://codeigniter.com 35 * @since Version 1.0.0 36 * @filesource 37 */ 38defined('BASEPATH') OR exit('No direct script access allowed'); 39 40/** 41 * Trackback Class 42 * 43 * Trackback Sending/Receiving Class 44 * 45 * @package CodeIgniter 46 * @subpackage Libraries 47 * @category Trackbacks 48 * @author EllisLab Dev Team 49 * @link https://codeigniter.com/user_guide/libraries/trackback.html 50 */ 51class CI_Trackback { 52 53 /** 54 * Character set 55 * 56 * @var string 57 */ 58 public $charset = 'UTF-8'; 59 60 /** 61 * Trackback data 62 * 63 * @var array 64 */ 65 public $data = array( 66 'url' => '', 67 'title' => '', 68 'excerpt' => '', 69 'blog_name' => '', 70 'charset' => '' 71 ); 72 73 /** 74 * Convert ASCII flag 75 * 76 * Whether to convert high-ASCII and MS Word 77 * characters to HTML entities. 78 * 79 * @var bool 80 */ 81 public $convert_ascii = TRUE; 82 83 /** 84 * Response 85 * 86 * @var string 87 */ 88 public $response = ''; 89 90 /** 91 * Error messages list 92 * 93 * @var string[] 94 */ 95 public $error_msg = array(); 96 97 // -------------------------------------------------------------------- 98 99 /** 100 * Constructor 101 * 102 * @return void 103 */ 104 public function __construct() 105 { 106 log_message('info', 'Trackback Class Initialized'); 107 } 108 109 // -------------------------------------------------------------------- 110 111 /** 112 * Send Trackback 113 * 114 * @param array 115 * @return bool 116 */ 117 public function send($tb_data) 118 { 119 if ( ! is_array($tb_data)) 120 { 121 $this->set_error('The send() method must be passed an array'); 122 return FALSE; 123 } 124 125 // Pre-process the Trackback Data 126 foreach (array('url', 'title', 'excerpt', 'blog_name', 'ping_url') as $item) 127 { 128 if ( ! isset($tb_data[$item])) 129 { 130 $this->set_error('Required item missing: '.$item); 131 return FALSE; 132 } 133 134 switch ($item) 135 { 136 case 'ping_url': 137 $$item = $this->extract_urls($tb_data[$item]); 138 break; 139 case 'excerpt': 140 $$item = $this->limit_characters($this->convert_xml(strip_tags(stripslashes($tb_data[$item])))); 141 break; 142 case 'url': 143 $$item = str_replace('-', '-', $this->convert_xml(strip_tags(stripslashes($tb_data[$item])))); 144 break; 145 default: 146 $$item = $this->convert_xml(strip_tags(stripslashes($tb_data[$item]))); 147 break; 148 } 149 150 // Convert High ASCII Characters 151 if ($this->convert_ascii === TRUE && in_array($item, array('excerpt', 'title', 'blog_name'), TRUE)) 152 { 153 $$item = $this->convert_ascii($$item); 154 } 155 } 156 157 // Build the Trackback data string 158 $charset = isset($tb_data['charset']) ? $tb_data['charset'] : $this->charset; 159 160 $data = 'url='.rawurlencode($url).'&title='.rawurlencode($title).'&blog_name='.rawurlencode($blog_name) 161 .'&excerpt='.rawurlencode($excerpt).'&charset='.rawurlencode($charset); 162 163 // Send Trackback(s) 164 $return = TRUE; 165 if (count($ping_url) > 0) 166 { 167 foreach ($ping_url as $url) 168 { 169 if ($this->process($url, $data) === FALSE) 170 { 171 $return = FALSE; 172 } 173 } 174 } 175 176 return $return; 177 } 178 179 // -------------------------------------------------------------------- 180 181 /** 182 * Receive Trackback Data 183 * 184 * This function simply validates the incoming TB data. 185 * It returns FALSE on failure and TRUE on success. 186 * If the data is valid it is set to the $this->data array 187 * so that it can be inserted into a database. 188 * 189 * @return bool 190 */ 191 public function receive() 192 { 193 foreach (array('url', 'title', 'blog_name', 'excerpt') as $val) 194 { 195 if (empty($_POST[$val])) 196 { 197 $this->set_error('The following required POST variable is missing: '.$val); 198 return FALSE; 199 } 200 201 $this->data['charset'] = isset($_POST['charset']) ? strtoupper(trim($_POST['charset'])) : 'auto'; 202 203 if ($val !== 'url' && MB_ENABLED === TRUE) 204 { 205 if (MB_ENABLED === TRUE) 206 { 207 $_POST[$val] = mb_convert_encoding($_POST[$val], $this->charset, $this->data['charset']); 208 } 209 elseif (ICONV_ENABLED === TRUE) 210 { 211 $_POST[$val] = @iconv($this->data['charset'], $this->charset.'//IGNORE', $_POST[$val]); 212 } 213 } 214 215 $_POST[$val] = ($val !== 'url') ? $this->convert_xml(strip_tags($_POST[$val])) : strip_tags($_POST[$val]); 216 217 if ($val === 'excerpt') 218 { 219 $_POST['excerpt'] = $this->limit_characters($_POST['excerpt']); 220 } 221 222 $this->data[$val] = $_POST[$val]; 223 } 224 225 return TRUE; 226 } 227 228 // -------------------------------------------------------------------- 229 230 /** 231 * Send Trackback Error Message 232 * 233 * Allows custom errors to be set. By default it 234 * sends the "incomplete information" error, as that's 235 * the most common one. 236 * 237 * @param string 238 * @return void 239 */ 240 public function send_error($message = 'Incomplete Information') 241 { 242 exit('<?xml version="1.0" encoding="utf-8"?'.">\n<response>\n<error>1</error>\n<message>".$message."</message>\n</response>"); 243 } 244 245 // -------------------------------------------------------------------- 246 247 /** 248 * Send Trackback Success Message 249 * 250 * This should be called when a trackback has been 251 * successfully received and inserted. 252 * 253 * @return void 254 */ 255 public function send_success() 256 { 257 exit('<?xml version="1.0" encoding="utf-8"?'.">\n<response>\n<error>0</error>\n</response>"); 258 } 259 260 // -------------------------------------------------------------------- 261 262 /** 263 * Fetch a particular item 264 * 265 * @param string 266 * @return string 267 */ 268 public function data($item) 269 { 270 return isset($this->data[$item]) ? $this->data[$item] : ''; 271 } 272 273 // -------------------------------------------------------------------- 274 275 /** 276 * Process Trackback 277 * 278 * Opens a socket connection and passes the data to 279 * the server. Returns TRUE on success, FALSE on failure 280 * 281 * @param string 282 * @param string 283 * @return bool 284 */ 285 public function process($url, $data) 286 { 287 $target = parse_url($url); 288 289 // Open the socket 290 if ( ! $fp = @fsockopen($target['host'], 80)) 291 { 292 $this->set_error('Invalid Connection: '.$url); 293 return FALSE; 294 } 295 296 // Build the path 297 $path = isset($target['path']) ? $target['path'] : $url; 298 empty($target['query']) OR $path .= '?'.$target['query']; 299 300 // Add the Trackback ID to the data string 301 if ($id = $this->get_id($url)) 302 { 303 $data = 'tb_id='.$id.'&'.$data; 304 } 305 306 // Transfer the data 307 fputs($fp, 'POST '.$path." HTTP/1.0\r\n"); 308 fputs($fp, 'Host: '.$target['host']."\r\n"); 309 fputs($fp, "Content-type: application/x-www-form-urlencoded\r\n"); 310 fputs($fp, 'Content-length: '.strlen($data)."\r\n"); 311 fputs($fp, "Connection: close\r\n\r\n"); 312 fputs($fp, $data); 313 314 // Was it successful? 315 316 $this->response = ''; 317 while ( ! feof($fp)) 318 { 319 $this->response .= fgets($fp, 128); 320 } 321 @fclose($fp); 322 323 if (stripos($this->response, '<error>0</error>') === FALSE) 324 { 325 $message = preg_match('/<message>(.*?)<\/message>/is', $this->response, $match) 326 ? trim($match[1]) 327 : 'An unknown error was encountered'; 328 $this->set_error($message); 329 return FALSE; 330 } 331 332 return TRUE; 333 } 334 335 // -------------------------------------------------------------------- 336 337 /** 338 * Extract Trackback URLs 339 * 340 * This function lets multiple trackbacks be sent. 341 * It takes a string of URLs (separated by comma or 342 * space) and puts each URL into an array 343 * 344 * @param string 345 * @return string 346 */ 347 public function extract_urls($urls) 348 { 349 // Remove the pesky white space and replace with a comma, then replace doubles. 350 $urls = str_replace(',,', ',', preg_replace('/\s*(\S+)\s*/', '\\1,', $urls)); 351 352 // Break into an array via commas and remove duplicates 353 $urls = array_unique(preg_split('/[,]/', rtrim($urls, ','))); 354 355 array_walk($urls, array($this, 'validate_url')); 356 return $urls; 357 } 358 359 // -------------------------------------------------------------------- 360 361 /** 362 * Validate URL 363 * 364 * Simply adds "http://" if missing 365 * 366 * @param string 367 * @return void 368 */ 369 public function validate_url(&$url) 370 { 371 $url = trim($url); 372 373 if (stripos($url, 'http') !== 0) 374 { 375 $url = 'http://'.$url; 376 } 377 } 378 379 // -------------------------------------------------------------------- 380 381 /** 382 * Find the Trackback URL's ID 383 * 384 * @param string 385 * @return string 386 */ 387 public function get_id($url) 388 { 389 $tb_id = ''; 390 391 if (strpos($url, '?') !== FALSE) 392 { 393 $tb_array = explode('/', $url); 394 $tb_end = $tb_array[count($tb_array)-1]; 395 396 if ( ! is_numeric($tb_end)) 397 { 398 $tb_end = $tb_array[count($tb_array)-2]; 399 } 400 401 $tb_array = explode('=', $tb_end); 402 $tb_id = $tb_array[count($tb_array)-1]; 403 } 404 else 405 { 406 $url = rtrim($url, '/'); 407 408 $tb_array = explode('/', $url); 409 $tb_id = $tb_array[count($tb_array)-1]; 410 411 if ( ! is_numeric($tb_id)) 412 { 413 $tb_id = $tb_array[count($tb_array)-2]; 414 } 415 } 416 417 return ctype_digit((string) $tb_id) ? $tb_id : FALSE; 418 } 419 420 // -------------------------------------------------------------------- 421 422 /** 423 * Convert Reserved XML characters to Entities 424 * 425 * @param string 426 * @return string 427 */ 428 public function convert_xml($str) 429 { 430 $temp = '__TEMP_AMPERSANDS__'; 431 432 $str = preg_replace(array('/&#(\d+);/', '/&(\w+);/'), $temp.'\\1;', $str); 433 434 $str = str_replace(array('&', '<', '>', '"', "'", '-'), 435 array('&', '<', '>', '"', ''', '-'), 436 $str); 437 438 return preg_replace(array('/'.$temp.'(\d+);/', '/'.$temp.'(\w+);/'), array('&#\\1;', '&\\1;'), $str); 439 } 440 441 // -------------------------------------------------------------------- 442 443 /** 444 * Character limiter 445 * 446 * Limits the string based on the character count. Will preserve complete words. 447 * 448 * @param string 449 * @param int 450 * @param string 451 * @return string 452 */ 453 public function limit_characters($str, $n = 500, $end_char = '…') 454 { 455 if (strlen($str) < $n) 456 { 457 return $str; 458 } 459 460 $str = preg_replace('/\s+/', ' ', str_replace(array("\r\n", "\r", "\n"), ' ', $str)); 461 462 if (strlen($str) <= $n) 463 { 464 return $str; 465 } 466 467 $out = ''; 468 foreach (explode(' ', trim($str)) as $val) 469 { 470 $out .= $val.' '; 471 if (strlen($out) >= $n) 472 { 473 return rtrim($out).$end_char; 474 } 475 } 476 } 477 478 // -------------------------------------------------------------------- 479 480 /** 481 * High ASCII to Entities 482 * 483 * Converts Hight ascii text and MS Word special chars 484 * to character entities 485 * 486 * @param string 487 * @return string 488 */ 489 public function convert_ascii($str) 490 { 491 $count = 1; 492 $out = ''; 493 $temp = array(); 494 495 for ($i = 0, $s = strlen($str); $i < $s; $i++) 496 { 497 $ordinal = ord($str[$i]); 498 499 if ($ordinal < 128) 500 { 501 $out .= $str[$i]; 502 } 503 else 504 { 505 if (count($temp) === 0) 506 { 507 $count = ($ordinal < 224) ? 2 : 3; 508 } 509 510 $temp[] = $ordinal; 511 512 if (count($temp) === $count) 513 { 514 $number = ($count === 3) 515 ? (($temp[0] % 16) * 4096) + (($temp[1] % 64) * 64) + ($temp[2] % 64) 516 : (($temp[0] % 32) * 64) + ($temp[1] % 64); 517 518 $out .= '&#'.$number.';'; 519 $count = 1; 520 $temp = array(); 521 } 522 } 523 } 524 525 return $out; 526 } 527 528 // -------------------------------------------------------------------- 529 530 /** 531 * Set error message 532 * 533 * @param string 534 * @return void 535 */ 536 public function set_error($msg) 537 { 538 log_message('error', $msg); 539 $this->error_msg[] = $msg; 540 } 541 542 // -------------------------------------------------------------------- 543 544 /** 545 * Show error messages 546 * 547 * @param string 548 * @param string 549 * @return string 550 */ 551 public function display_errors($open = '<p>', $close = '</p>') 552 { 553 return (count($this->error_msg) > 0) ? $open.implode($close.$open, $this->error_msg).$close : ''; 554 } 555 556} 557