You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

556 lines
13 KiB

7 years ago
  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 - 2017, 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 - 2017, 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. */
  38. defined('BASEPATH') OR exit('No direct script access allowed');
  39. /**
  40. * Trackback Class
  41. *
  42. * Trackback Sending/Receiving Class
  43. *
  44. * @package CodeIgniter
  45. * @subpackage Libraries
  46. * @category Trackbacks
  47. * @author EllisLab Dev Team
  48. * @link https://codeigniter.com/user_guide/libraries/trackback.html
  49. */
  50. class CI_Trackback {
  51. /**
  52. * Character set
  53. *
  54. * @var string
  55. */
  56. public $charset = 'UTF-8';
  57. /**
  58. * Trackback data
  59. *
  60. * @var array
  61. */
  62. public $data = array(
  63. 'url' => '',
  64. 'title' => '',
  65. 'excerpt' => '',
  66. 'blog_name' => '',
  67. 'charset' => ''
  68. );
  69. /**
  70. * Convert ASCII flag
  71. *
  72. * Whether to convert high-ASCII and MS Word
  73. * characters to HTML entities.
  74. *
  75. * @var bool
  76. */
  77. public $convert_ascii = TRUE;
  78. /**
  79. * Response
  80. *
  81. * @var string
  82. */
  83. public $response = '';
  84. /**
  85. * Error messages list
  86. *
  87. * @var string[]
  88. */
  89. public $error_msg = array();
  90. // --------------------------------------------------------------------
  91. /**
  92. * Constructor
  93. *
  94. * @return void
  95. */
  96. public function __construct()
  97. {
  98. log_message('info', 'Trackback Class Initialized');
  99. }
  100. // --------------------------------------------------------------------
  101. /**
  102. * Send Trackback
  103. *
  104. * @param array
  105. * @return bool
  106. */
  107. public function send($tb_data)
  108. {
  109. if ( ! is_array($tb_data))
  110. {
  111. $this->set_error('The send() method must be passed an array');
  112. return FALSE;
  113. }
  114. // Pre-process the Trackback Data
  115. foreach (array('url', 'title', 'excerpt', 'blog_name', 'ping_url') as $item)
  116. {
  117. if ( ! isset($tb_data[$item]))
  118. {
  119. $this->set_error('Required item missing: '.$item);
  120. return FALSE;
  121. }
  122. switch ($item)
  123. {
  124. case 'ping_url':
  125. $$item = $this->extract_urls($tb_data[$item]);
  126. break;
  127. case 'excerpt':
  128. $$item = $this->limit_characters($this->convert_xml(strip_tags(stripslashes($tb_data[$item]))));
  129. break;
  130. case 'url':
  131. $$item = str_replace('&#45;', '-', $this->convert_xml(strip_tags(stripslashes($tb_data[$item]))));
  132. break;
  133. default:
  134. $$item = $this->convert_xml(strip_tags(stripslashes($tb_data[$item])));
  135. break;
  136. }
  137. // Convert High ASCII Characters
  138. if ($this->convert_ascii === TRUE && in_array($item, array('excerpt', 'title', 'blog_name'), TRUE))
  139. {
  140. $$item = $this->convert_ascii($$item);
  141. }
  142. }
  143. // Build the Trackback data string
  144. $charset = isset($tb_data['charset']) ? $tb_data['charset'] : $this->charset;
  145. $data = 'url='.rawurlencode($url).'&title='.rawurlencode($title).'&blog_name='.rawurlencode($blog_name)
  146. .'&excerpt='.rawurlencode($excerpt).'&charset='.rawurlencode($charset);
  147. // Send Trackback(s)
  148. $return = TRUE;
  149. if (count($ping_url) > 0)
  150. {
  151. foreach ($ping_url as $url)
  152. {
  153. if ($this->process($url, $data) === FALSE)
  154. {
  155. $return = FALSE;
  156. }
  157. }
  158. }
  159. return $return;
  160. }
  161. // --------------------------------------------------------------------
  162. /**
  163. * Receive Trackback Data
  164. *
  165. * This function simply validates the incoming TB data.
  166. * It returns FALSE on failure and TRUE on success.
  167. * If the data is valid it is set to the $this->data array
  168. * so that it can be inserted into a database.
  169. *
  170. * @return bool
  171. */
  172. public function receive()
  173. {
  174. foreach (array('url', 'title', 'blog_name', 'excerpt') as $val)
  175. {
  176. if (empty($_POST[$val]))
  177. {
  178. $this->set_error('The following required POST variable is missing: '.$val);
  179. return FALSE;
  180. }
  181. $this->data['charset'] = isset($_POST['charset']) ? strtoupper(trim($_POST['charset'])) : 'auto';
  182. if ($val !== 'url' && MB_ENABLED === TRUE)
  183. {
  184. if (MB_ENABLED === TRUE)
  185. {
  186. $_POST[$val] = mb_convert_encoding($_POST[$val], $this->charset, $this->data['charset']);
  187. }
  188. elseif (ICONV_ENABLED === TRUE)
  189. {
  190. $_POST[$val] = @iconv($this->data['charset'], $this->charset.'//IGNORE', $_POST[$val]);
  191. }
  192. }
  193. $_POST[$val] = ($val !== 'url') ? $this->convert_xml(strip_tags($_POST[$val])) : strip_tags($_POST[$val]);
  194. if ($val === 'excerpt')
  195. {
  196. $_POST['excerpt'] = $this->limit_characters($_POST['excerpt']);
  197. }
  198. $this->data[$val] = $_POST[$val];
  199. }
  200. return TRUE;
  201. }
  202. // --------------------------------------------------------------------
  203. /**
  204. * Send Trackback Error Message
  205. *
  206. * Allows custom errors to be set. By default it
  207. * sends the "incomplete information" error, as that's
  208. * the most common one.
  209. *
  210. * @param string
  211. * @return void
  212. */
  213. public function send_error($message = 'Incomplete Information')
  214. {
  215. exit('<?xml version="1.0" encoding="utf-8"?'.">\n<response>\n<error>1</error>\n<message>".$message."</message>\n</response>");
  216. }
  217. // --------------------------------------------------------------------
  218. /**
  219. * Send Trackback Success Message
  220. *
  221. * This should be called when a trackback has been
  222. * successfully received and inserted.
  223. *
  224. * @return void
  225. */
  226. public function send_success()
  227. {
  228. exit('<?xml version="1.0" encoding="utf-8"?'.">\n<response>\n<error>0</error>\n</response>");
  229. }
  230. // --------------------------------------------------------------------
  231. /**
  232. * Fetch a particular item
  233. *
  234. * @param string
  235. * @return string
  236. */
  237. public function data($item)
  238. {
  239. return isset($this->data[$item]) ? $this->data[$item] : '';
  240. }
  241. // --------------------------------------------------------------------
  242. /**
  243. * Process Trackback
  244. *
  245. * Opens a socket connection and passes the data to
  246. * the server. Returns TRUE on success, FALSE on failure
  247. *
  248. * @param string
  249. * @param string
  250. * @return bool
  251. */
  252. public function process($url, $data)
  253. {
  254. $target = parse_url($url);
  255. // Open the socket
  256. if ( ! $fp = @fsockopen($target['host'], 80))
  257. {
  258. $this->set_error('Invalid Connection: '.$url);
  259. return FALSE;
  260. }
  261. // Build the path
  262. $path = isset($target['path']) ? $target['path'] : $url;
  263. empty($target['query']) OR $path .= '?'.$target['query'];
  264. // Add the Trackback ID to the data string
  265. if ($id = $this->get_id($url))
  266. {
  267. $data = 'tb_id='.$id.'&'.$data;
  268. }
  269. // Transfer the data
  270. fputs($fp, 'POST '.$path." HTTP/1.0\r\n");
  271. fputs($fp, 'Host: '.$target['host']."\r\n");
  272. fputs($fp, "Content-type: application/x-www-form-urlencoded\r\n");
  273. fputs($fp, 'Content-length: '.strlen($data)."\r\n");
  274. fputs($fp, "Connection: close\r\n\r\n");
  275. fputs($fp, $data);
  276. // Was it successful?
  277. $this->response = '';
  278. while ( ! feof($fp))
  279. {
  280. $this->response .= fgets($fp, 128);
  281. }
  282. @fclose($fp);
  283. if (stripos($this->response, '<error>0</error>') === FALSE)
  284. {
  285. $message = preg_match('/<message>(.*?)<\/message>/is', $this->response, $match)
  286. ? trim($match[1])
  287. : 'An unknown error was encountered';
  288. $this->set_error($message);
  289. return FALSE;
  290. }
  291. return TRUE;
  292. }
  293. // --------------------------------------------------------------------
  294. /**
  295. * Extract Trackback URLs
  296. *
  297. * This function lets multiple trackbacks be sent.
  298. * It takes a string of URLs (separated by comma or
  299. * space) and puts each URL into an array
  300. *
  301. * @param string
  302. * @return string
  303. */
  304. public function extract_urls($urls)
  305. {
  306. // Remove the pesky white space and replace with a comma, then replace doubles.
  307. $urls = str_replace(',,', ',', preg_replace('/\s*(\S+)\s*/', '\\1,', $urls));
  308. // Break into an array via commas and remove duplicates
  309. $urls = array_unique(preg_split('/[,]/', rtrim($urls, ',')));
  310. array_walk($urls, array($this, 'validate_url'));
  311. return $urls;
  312. }
  313. // --------------------------------------------------------------------
  314. /**
  315. * Validate URL
  316. *
  317. * Simply adds "http://" if missing
  318. *
  319. * @param string
  320. * @return void
  321. */
  322. public function validate_url(&$url)
  323. {
  324. $url = trim($url);
  325. if (stripos($url, 'http') !== 0)
  326. {
  327. $url = 'http://'.$url;
  328. }
  329. }
  330. // --------------------------------------------------------------------
  331. /**
  332. * Find the Trackback URL's ID
  333. *
  334. * @param string
  335. * @return string
  336. */
  337. public function get_id($url)
  338. {
  339. $tb_id = '';
  340. if (strpos($url, '?') !== FALSE)
  341. {
  342. $tb_array = explode('/', $url);
  343. $tb_end = $tb_array[count($tb_array)-1];
  344. if ( ! is_numeric($tb_end))
  345. {
  346. $tb_end = $tb_array[count($tb_array)-2];
  347. }
  348. $tb_array = explode('=', $tb_end);
  349. $tb_id = $tb_array[count($tb_array)-1];
  350. }
  351. else
  352. {
  353. $url = rtrim($url, '/');
  354. $tb_array = explode('/', $url);
  355. $tb_id = $tb_array[count($tb_array)-1];
  356. if ( ! is_numeric($tb_id))
  357. {
  358. $tb_id = $tb_array[count($tb_array)-2];
  359. }
  360. }
  361. return ctype_digit((string) $tb_id) ? $tb_id : FALSE;
  362. }
  363. // --------------------------------------------------------------------
  364. /**
  365. * Convert Reserved XML characters to Entities
  366. *
  367. * @param string
  368. * @return string
  369. */
  370. public function convert_xml($str)
  371. {
  372. $temp = '__TEMP_AMPERSANDS__';
  373. $str = preg_replace(array('/&#(\d+);/', '/&(\w+);/'), $temp.'\\1;', $str);
  374. $str = str_replace(array('&', '<', '>', '"', "'", '-'),
  375. array('&amp;', '&lt;', '&gt;', '&quot;', '&#39;', '&#45;'),
  376. $str);
  377. return preg_replace(array('/'.$temp.'(\d+);/', '/'.$temp.'(\w+);/'), array('&#\\1;', '&\\1;'), $str);
  378. }
  379. // --------------------------------------------------------------------
  380. /**
  381. * Character limiter
  382. *
  383. * Limits the string based on the character count. Will preserve complete words.
  384. *
  385. * @param string
  386. * @param int
  387. * @param string
  388. * @return string
  389. */
  390. public function limit_characters($str, $n = 500, $end_char = '&#8230;')
  391. {
  392. if (strlen($str) < $n)
  393. {
  394. return $str;
  395. }
  396. $str = preg_replace('/\s+/', ' ', str_replace(array("\r\n", "\r", "\n"), ' ', $str));
  397. if (strlen($str) <= $n)
  398. {
  399. return $str;
  400. }
  401. $out = '';
  402. foreach (explode(' ', trim($str)) as $val)
  403. {
  404. $out .= $val.' ';
  405. if (strlen($out) >= $n)
  406. {
  407. return rtrim($out).$end_char;
  408. }
  409. }
  410. }
  411. // --------------------------------------------------------------------
  412. /**
  413. * High ASCII to Entities
  414. *
  415. * Converts Hight ascii text and MS Word special chars
  416. * to character entities
  417. *
  418. * @param string
  419. * @return string
  420. */
  421. public function convert_ascii($str)
  422. {
  423. $count = 1;
  424. $out = '';
  425. $temp = array();
  426. for ($i = 0, $s = strlen($str); $i < $s; $i++)
  427. {
  428. $ordinal = ord($str[$i]);
  429. if ($ordinal < 128)
  430. {
  431. $out .= $str[$i];
  432. }
  433. else
  434. {
  435. if (count($temp) === 0)
  436. {
  437. $count = ($ordinal < 224) ? 2 : 3;
  438. }
  439. $temp[] = $ordinal;
  440. if (count($temp) === $count)
  441. {
  442. $number = ($count === 3)
  443. ? (($temp[0] % 16) * 4096) + (($temp[1] % 64) * 64) + ($temp[2] % 64)
  444. : (($temp[0] % 32) * 64) + ($temp[1] % 64);
  445. $out .= '&#'.$number.';';
  446. $count = 1;
  447. $temp = array();
  448. }
  449. }
  450. }
  451. return $out;
  452. }
  453. // --------------------------------------------------------------------
  454. /**
  455. * Set error message
  456. *
  457. * @param string
  458. * @return void
  459. */
  460. public function set_error($msg)
  461. {
  462. log_message('error', $msg);
  463. $this->error_msg[] = $msg;
  464. }
  465. // --------------------------------------------------------------------
  466. /**
  467. * Show error messages
  468. *
  469. * @param string
  470. * @param string
  471. * @return string
  472. */
  473. public function display_errors($open = '<p>', $close = '</p>')
  474. {
  475. return (count($this->error_msg) > 0) ? $open.implode($close.$open, $this->error_msg).$close : '';
  476. }
  477. }