Friendica Communications Platform (please note that this is a clone of the repository at github, issues are handled there) https://friendi.ca
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.

991 line
26KB

  1. <?php
  2. /**
  3. * @file include/network.php
  4. */
  5. use Friendica\Core\Config;
  6. require_once("include/xml.php");
  7. require_once('include/Probe.php');
  8. /**
  9. * @brief Curl wrapper
  10. *
  11. * If binary flag is true, return binary results.
  12. * Set the cookiejar argument to a string (e.g. "/tmp/friendica-cookies.txt")
  13. * to preserve cookies from one request to the next.
  14. *
  15. * @param string $url URL to fetch
  16. * @param boolean $binary default false
  17. * TRUE if asked to return binary results (file download)
  18. * @param integer $redirects The recursion counter for internal use - default 0
  19. * @param integer $timeout Timeout in seconds, default system config value or 60 seconds
  20. * @param string $accept_content supply Accept: header with 'accept_content' as the value
  21. * @param string $cookiejar Path to cookie jar file
  22. *
  23. * @return string The fetched content
  24. */
  25. function fetch_url($url,$binary = false, &$redirects = 0, $timeout = 0, $accept_content=Null, $cookiejar = 0) {
  26. $ret = z_fetch_url(
  27. $url,
  28. $binary,
  29. $redirects,
  30. array('timeout'=>$timeout,
  31. 'accept_content'=>$accept_content,
  32. 'cookiejar'=>$cookiejar
  33. ));
  34. return($ret['body']);
  35. }
  36. /**
  37. * @brief fetches an URL.
  38. *
  39. * @param string $url URL to fetch
  40. * @param boolean $binary default false
  41. * TRUE if asked to return binary results (file download)
  42. * @param int $redirects The recursion counter for internal use - default 0
  43. * @param array $opts (optional parameters) assoziative array with:
  44. * 'accept_content' => supply Accept: header with 'accept_content' as the value
  45. * 'timeout' => int Timeout in seconds, default system config value or 60 seconds
  46. * 'http_auth' => username:password
  47. * 'novalidate' => do not validate SSL certs, default is to validate using our CA list
  48. * 'nobody' => only return the header
  49. * 'cookiejar' => path to cookie jar file
  50. *
  51. * @return array an assoziative array with:
  52. * int 'return_code' => HTTP return code or 0 if timeout or failure
  53. * boolean 'success' => boolean true (if HTTP 2xx result) or false
  54. * string 'redirect_url' => in case of redirect, content was finally retrieved from this URL
  55. * string 'header' => HTTP headers
  56. * string 'body' => fetched content
  57. */
  58. function z_fetch_url($url, $binary = false, &$redirects = 0, $opts = array()) {
  59. $ret = array('return_code' => 0, 'success' => false, 'header' => '', 'body' => '');
  60. $stamp1 = microtime(true);
  61. $a = get_app();
  62. if (blocked_url($url)) {
  63. logger('z_fetch_url: domain of ' . $url . ' is blocked', LOGGER_DATA);
  64. return $ret;
  65. }
  66. $ch = @curl_init($url);
  67. if (($redirects > 8) || (!$ch)) {
  68. return $ret;
  69. }
  70. @curl_setopt($ch, CURLOPT_HEADER, true);
  71. if (x($opts, "cookiejar")) {
  72. curl_setopt($ch, CURLOPT_COOKIEJAR, $opts["cookiejar"]);
  73. curl_setopt($ch, CURLOPT_COOKIEFILE, $opts["cookiejar"]);
  74. }
  75. // These settings aren't needed. We're following the location already.
  76. // @curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
  77. // @curl_setopt($ch, CURLOPT_MAXREDIRS, 5);
  78. if (x($opts, 'accept_content')) {
  79. curl_setopt($ch, CURLOPT_HTTPHEADER, array(
  80. 'Accept: ' . $opts['accept_content']
  81. ));
  82. }
  83. @curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  84. @curl_setopt($ch, CURLOPT_USERAGENT, $a->get_useragent());
  85. $range = intval(Config::get('system', 'curl_range_bytes', 0));
  86. if ($range > 0) {
  87. @curl_setopt($ch, CURLOPT_RANGE, '0-' . $range);
  88. }
  89. if (x($opts, 'headers')) {
  90. @curl_setopt($ch, CURLOPT_HTTPHEADER, $opts['headers']);
  91. }
  92. if (x($opts, 'nobody')) {
  93. @curl_setopt($ch, CURLOPT_NOBODY, $opts['nobody']);
  94. }
  95. if (x($opts, 'timeout')) {
  96. @curl_setopt($ch, CURLOPT_TIMEOUT, $opts['timeout']);
  97. } else {
  98. $curl_time = intval(get_config('system', 'curl_timeout'));
  99. @curl_setopt($ch, CURLOPT_TIMEOUT, (($curl_time !== false) ? $curl_time : 60));
  100. }
  101. // by default we will allow self-signed certs
  102. // but you can override this
  103. $check_cert = get_config('system', 'verifyssl');
  104. @curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, (($check_cert) ? true : false));
  105. if ($check_cert) {
  106. @curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
  107. }
  108. $proxy = get_config('system', 'proxy');
  109. if (strlen($proxy)) {
  110. @curl_setopt($ch, CURLOPT_HTTPPROXYTUNNEL, 1);
  111. @curl_setopt($ch, CURLOPT_PROXY, $proxy);
  112. $proxyuser = @get_config('system', 'proxyuser');
  113. if (strlen($proxyuser)) {
  114. @curl_setopt($ch, CURLOPT_PROXYUSERPWD, $proxyuser);
  115. }
  116. }
  117. if ($binary) {
  118. @curl_setopt($ch, CURLOPT_BINARYTRANSFER, 1);
  119. }
  120. $a->set_curl_code(0);
  121. // don't let curl abort the entire application
  122. // if it throws any errors.
  123. $s = @curl_exec($ch);
  124. if (curl_errno($ch) !== CURLE_OK) {
  125. logger('fetch_url error fetching ' . $url . ': ' . curl_error($ch), LOGGER_NORMAL);
  126. }
  127. $ret['errno'] = curl_errno($ch);
  128. $base = $s;
  129. $curl_info = @curl_getinfo($ch);
  130. $http_code = $curl_info['http_code'];
  131. logger('fetch_url ' . $url . ': ' . $http_code . " " . $s, LOGGER_DATA);
  132. $header = '';
  133. // Pull out multiple headers, e.g. proxy and continuation headers
  134. // allow for HTTP/2.x without fixing code
  135. while (preg_match('/^HTTP\/[1-2].+? [1-5][0-9][0-9]/', $base)) {
  136. $chunk = substr($base, 0, strpos($base, "\r\n\r\n") + 4);
  137. $header .= $chunk;
  138. $base = substr($base, strlen($chunk));
  139. }
  140. $a->set_curl_code($http_code);
  141. $a->set_curl_content_type($curl_info['content_type']);
  142. $a->set_curl_headers($header);
  143. if ($http_code == 301 || $http_code == 302 || $http_code == 303 || $http_code == 307) {
  144. $new_location_info = @parse_url($curl_info['redirect_url']);
  145. $old_location_info = @parse_url($curl_info['url']);
  146. $newurl = $curl_info['redirect_url'];
  147. if (($new_location_info['path'] == '') AND ( $new_location_info['host'] != '')) {
  148. $newurl = $new_location_info['scheme'] . '://' . $new_location_info['host'] . $old_location_info['path'];
  149. }
  150. $matches = array();
  151. if (preg_match('/(Location:|URI:)(.*?)\n/i', $header, $matches)) {
  152. $newurl = trim(array_pop($matches));
  153. }
  154. if (strpos($newurl, '/') === 0) {
  155. $newurl = $old_location_info['scheme'] . '://' . $old_location_info['host'] . $newurl;
  156. }
  157. if (filter_var($newurl, FILTER_VALIDATE_URL)) {
  158. $redirects++;
  159. @curl_close($ch);
  160. return z_fetch_url($newurl, $binary, $redirects, $opts);
  161. }
  162. }
  163. $a->set_curl_code($http_code);
  164. $a->set_curl_content_type($curl_info['content_type']);
  165. $body = substr($s, strlen($header));
  166. $rc = intval($http_code);
  167. $ret['return_code'] = $rc;
  168. $ret['success'] = (($rc >= 200 && $rc <= 299) ? true : false);
  169. $ret['redirect_url'] = $url;
  170. if (!$ret['success']) {
  171. $ret['error'] = curl_error($ch);
  172. $ret['debug'] = $curl_info;
  173. logger('z_fetch_url: error: ' . $url . ': ' . $ret['error'], LOGGER_DEBUG);
  174. logger('z_fetch_url: debug: ' . print_r($curl_info, true), LOGGER_DATA);
  175. }
  176. $ret['body'] = substr($s, strlen($header));
  177. $ret['header'] = $header;
  178. if (x($opts, 'debug')) {
  179. $ret['debug'] = $curl_info;
  180. }
  181. @curl_close($ch);
  182. $a->save_timestamp($stamp1, 'network');
  183. return($ret);
  184. }
  185. /**
  186. * @brief Send POST request to $url
  187. *
  188. * @param string $url URL to post
  189. * @param mixed $params array of POST variables
  190. * @param string $headers HTTP headers
  191. * @param integer $redirects Recursion counter for internal use - default = 0
  192. * @param integer $timeout The timeout in seconds, default system config value or 60 seconds
  193. *
  194. * @return string The content
  195. */
  196. function post_url($url, $params, $headers = null, &$redirects = 0, $timeout = 0) {
  197. $stamp1 = microtime(true);
  198. if (blocked_url($url)) {
  199. logger('post_url: domain of ' . $url . ' is blocked', LOGGER_DATA);
  200. return false;
  201. }
  202. $a = get_app();
  203. $ch = curl_init($url);
  204. if (($redirects > 8) || (!$ch)) {
  205. return false;
  206. }
  207. logger('post_url: start ' . $url, LOGGER_DATA);
  208. curl_setopt($ch, CURLOPT_HEADER, true);
  209. curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  210. curl_setopt($ch, CURLOPT_POST, 1);
  211. curl_setopt($ch, CURLOPT_POSTFIELDS, $params);
  212. curl_setopt($ch, CURLOPT_USERAGENT, $a->get_useragent());
  213. if (intval($timeout)) {
  214. curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
  215. } else {
  216. $curl_time = intval(get_config('system', 'curl_timeout'));
  217. curl_setopt($ch, CURLOPT_TIMEOUT, (($curl_time !== false) ? $curl_time : 60));
  218. }
  219. if (defined('LIGHTTPD')) {
  220. if (!is_array($headers)) {
  221. $headers = array('Expect:');
  222. } else {
  223. if (!in_array('Expect:', $headers)) {
  224. array_push($headers, 'Expect:');
  225. }
  226. }
  227. }
  228. if ($headers) {
  229. curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
  230. }
  231. $check_cert = get_config('system', 'verifyssl');
  232. curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, (($check_cert) ? true : false));
  233. if ($check_cert) {
  234. @curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
  235. }
  236. $proxy = get_config('system', 'proxy');
  237. if (strlen($proxy)) {
  238. curl_setopt($ch, CURLOPT_HTTPPROXYTUNNEL, 1);
  239. curl_setopt($ch, CURLOPT_PROXY, $proxy);
  240. $proxyuser = get_config('system', 'proxyuser');
  241. if (strlen($proxyuser)) {
  242. curl_setopt($ch, CURLOPT_PROXYUSERPWD, $proxyuser);
  243. }
  244. }
  245. $a->set_curl_code(0);
  246. // don't let curl abort the entire application
  247. // if it throws any errors.
  248. $s = @curl_exec($ch);
  249. $base = $s;
  250. $curl_info = curl_getinfo($ch);
  251. $http_code = $curl_info['http_code'];
  252. logger('post_url: result ' . $http_code . ' - ' . $url, LOGGER_DATA);
  253. $header = '';
  254. // Pull out multiple headers, e.g. proxy and continuation headers
  255. // allow for HTTP/2.x without fixing code
  256. while (preg_match('/^HTTP\/[1-2].+? [1-5][0-9][0-9]/', $base)) {
  257. $chunk = substr($base, 0, strpos($base, "\r\n\r\n") + 4);
  258. $header .= $chunk;
  259. $base = substr($base, strlen($chunk));
  260. }
  261. if ($http_code == 301 || $http_code == 302 || $http_code == 303 || $http_code == 307) {
  262. $matches = array();
  263. preg_match('/(Location:|URI:)(.*?)\n/', $header, $matches);
  264. $newurl = trim(array_pop($matches));
  265. if (strpos($newurl, '/') === 0) {
  266. $newurl = $old_location_info['scheme'] . '://' . $old_location_info['host'] . $newurl;
  267. }
  268. if (filter_var($newurl, FILTER_VALIDATE_URL)) {
  269. $redirects++;
  270. logger('post_url: redirect ' . $url . ' to ' . $newurl);
  271. return post_url($newurl, $params, $headers, $redirects, $timeout);
  272. }
  273. }
  274. $a->set_curl_code($http_code);
  275. $body = substr($s, strlen($header));
  276. $a->set_curl_headers($header);
  277. curl_close($ch);
  278. $a->save_timestamp($stamp1, 'network');
  279. logger('post_url: end ' . $url, LOGGER_DATA);
  280. return $body;
  281. }
  282. // Generic XML return
  283. // Outputs a basic dfrn XML status structure to STDOUT, with a <status> variable
  284. // of $st and an optional text <message> of $message and terminates the current process.
  285. function xml_status($st, $message = '') {
  286. $xml_message = ((strlen($message)) ? "\t<message>" . xmlify($message) . "</message>\r\n" : '');
  287. if($st)
  288. logger('xml_status returning non_zero: ' . $st . " message=" . $message);
  289. header( "Content-type: text/xml" );
  290. echo '<?xml version="1.0" encoding="UTF-8"?>'."\r\n";
  291. echo "<result>\r\n\t<status>$st</status>\r\n$xml_message</result>\r\n";
  292. killme();
  293. }
  294. /**
  295. * @brief Send HTTP status header and exit.
  296. *
  297. * @param integer $val HTTP status result value
  298. * @param array $description optional message
  299. * 'title' => header title
  300. * 'description' => optional message
  301. */
  302. /**
  303. * @brief Send HTTP status header and exit.
  304. *
  305. * @param integer $val HTTP status result value
  306. * @param array $description optional message
  307. * 'title' => header title
  308. * 'description' => optional message
  309. */
  310. function http_status_exit($val, $description = array()) {
  311. $err = '';
  312. if($val >= 400) {
  313. $err = 'Error';
  314. if (!isset($description["title"]))
  315. $description["title"] = $err." ".$val;
  316. }
  317. if($val >= 200 && $val < 300)
  318. $err = 'OK';
  319. logger('http_status_exit ' . $val);
  320. header($_SERVER["SERVER_PROTOCOL"] . ' ' . $val . ' ' . $err);
  321. if (isset($description["title"])) {
  322. $tpl = get_markup_template('http_status.tpl');
  323. echo replace_macros($tpl, array('$title' => $description["title"],
  324. '$description' => $description["description"]));
  325. }
  326. killme();
  327. }
  328. /**
  329. * @brief Check URL to se if ts's real
  330. *
  331. * Take a URL from the wild, prepend http:// if necessary
  332. * and check DNS to see if it's real (or check if is a valid IP address)
  333. *
  334. * @param string $url The URL to be validated
  335. * @return boolean True if it's a valid URL, fals if something wrong with it
  336. */
  337. function validate_url(&$url) {
  338. if(get_config('system','disable_url_validation'))
  339. return true;
  340. // no naked subdomains (allow localhost for tests)
  341. if(strpos($url,'.') === false && strpos($url,'/localhost/') === false)
  342. return false;
  343. if(substr($url,0,4) != 'http')
  344. $url = 'http://' . $url;
  345. /// @TODO Really supress function outcomes? Why not find them + debug them?
  346. $h = @parse_url($url);
  347. if((is_array($h)) && (dns_get_record($h['host'], DNS_A + DNS_CNAME + DNS_PTR) || filter_var($h['host'], FILTER_VALIDATE_IP) )) {
  348. return true;
  349. }
  350. return false;
  351. }
  352. /**
  353. * @brief Checks that email is an actual resolvable internet address
  354. *
  355. * @param string $addr The email address
  356. * @return boolean True if it's a valid email address, false if it's not
  357. */
  358. function validate_email($addr) {
  359. if(get_config('system','disable_email_validation'))
  360. return true;
  361. if(! strpos($addr,'@'))
  362. return false;
  363. $h = substr($addr,strpos($addr,'@') + 1);
  364. if(($h) && (dns_get_record($h, DNS_A + DNS_CNAME + DNS_PTR + DNS_MX) || filter_var($h, FILTER_VALIDATE_IP) )) {
  365. return true;
  366. }
  367. return false;
  368. }
  369. /**
  370. * @brief Check if URL is allowed
  371. *
  372. * Check $url against our list of allowed sites,
  373. * wildcards allowed. If allowed_sites is unset return true;
  374. *
  375. * @param string $url URL which get tested
  376. * @return boolean True if url is allowed otherwise return false
  377. */
  378. function allowed_url($url) {
  379. $h = @parse_url($url);
  380. if (! $h) {
  381. return false;
  382. }
  383. $str_allowed = Config::get('system', 'allowed_sites');
  384. if (! $str_allowed) {
  385. return true;
  386. }
  387. $found = false;
  388. $host = strtolower($h['host']);
  389. // always allow our own site
  390. if ($host == strtolower($_SERVER['SERVER_NAME'])) {
  391. return true;
  392. }
  393. $fnmatch = function_exists('fnmatch');
  394. $allowed = explode(',', $str_allowed);
  395. if (count($allowed)) {
  396. foreach ($allowed as $a) {
  397. $pat = strtolower(trim($a));
  398. if (($fnmatch && fnmatch($pat, $host)) || ($pat == $host)) {
  399. $found = true;
  400. break;
  401. }
  402. }
  403. }
  404. return $found;
  405. }
  406. /**
  407. * Checks if the provided url domain is on the domain blocklist.
  408. * Returns true if it is or malformed URL, false if not.
  409. *
  410. * @param string $url The url to check the domain from
  411. * @return boolean
  412. */
  413. function blocked_url($url) {
  414. $h = @parse_url($url);
  415. if (! $h) {
  416. return true;
  417. }
  418. $domain_blocklist = Config::get('system', 'blocklist', array());
  419. if (! $domain_blocklist) {
  420. return false;
  421. }
  422. $host = strtolower($h['host']);
  423. foreach ($domain_blocklist as $domain_block) {
  424. if (strtolower($domain_block['domain']) == $host) {
  425. return true;
  426. }
  427. }
  428. return false;
  429. }
  430. /**
  431. * @brief Check if email address is allowed to register here.
  432. *
  433. * Compare against our list (wildcards allowed).
  434. *
  435. * @param type $email
  436. * @return boolean False if not allowed, true if allowed
  437. * or if allowed list is not configured
  438. */
  439. function allowed_email($email) {
  440. $domain = strtolower(substr($email,strpos($email,'@') + 1));
  441. if(! $domain)
  442. return false;
  443. $str_allowed = get_config('system','allowed_email');
  444. if(! $str_allowed)
  445. return true;
  446. $found = false;
  447. $fnmatch = function_exists('fnmatch');
  448. $allowed = explode(',',$str_allowed);
  449. if(count($allowed)) {
  450. foreach($allowed as $a) {
  451. $pat = strtolower(trim($a));
  452. if(($fnmatch && fnmatch($pat,$domain)) || ($pat == $domain)) {
  453. $found = true;
  454. break;
  455. }
  456. }
  457. }
  458. return $found;
  459. }
  460. function avatar_img($email) {
  461. $avatar['size'] = 175;
  462. $avatar['email'] = $email;
  463. $avatar['url'] = '';
  464. $avatar['success'] = false;
  465. call_hooks('avatar_lookup', $avatar);
  466. if (! $avatar['success']) {
  467. $avatar['url'] = App::get_baseurl() . '/images/person-175.jpg';
  468. }
  469. logger('Avatar: ' . $avatar['email'] . ' ' . $avatar['url'], LOGGER_DEBUG);
  470. return $avatar['url'];
  471. }
  472. function parse_xml_string($s,$strict = true) {
  473. /// @todo Move this function to the xml class
  474. if($strict) {
  475. if(! strstr($s,'<?xml'))
  476. return false;
  477. $s2 = substr($s,strpos($s,'<?xml'));
  478. }
  479. else
  480. $s2 = $s;
  481. libxml_use_internal_errors(true);
  482. $x = @simplexml_load_string($s2);
  483. if (! $x) {
  484. logger('libxml: parse: error: ' . $s2, LOGGER_DATA);
  485. foreach (libxml_get_errors() as $err) {
  486. logger('libxml: parse: ' . $err->code." at ".$err->line.":".$err->column." : ".$err->message, LOGGER_DATA);
  487. }
  488. libxml_clear_errors();
  489. }
  490. return $x;
  491. }
  492. function scale_external_images($srctext, $include_link = true, $scale_replace = false) {
  493. // Suppress "view full size"
  494. if (intval(get_config('system','no_view_full_size'))) {
  495. $include_link = false;
  496. }
  497. $a = get_app();
  498. // Picture addresses can contain special characters
  499. $s = htmlspecialchars_decode($srctext);
  500. $matches = null;
  501. $c = preg_match_all('/\[img.*?\](.*?)\[\/img\]/ism',$s,$matches,PREG_SET_ORDER);
  502. if ($c) {
  503. require_once('include/Photo.php');
  504. foreach ($matches as $mtch) {
  505. logger('scale_external_image: ' . $mtch[1]);
  506. $hostname = str_replace('www.','',substr(App::get_baseurl(),strpos(App::get_baseurl(),'://')+3));
  507. if (stristr($mtch[1],$hostname)) {
  508. continue;
  509. }
  510. // $scale_replace, if passed, is an array of two elements. The
  511. // first is the name of the full-size image. The second is the
  512. // name of a remote, scaled-down version of the full size image.
  513. // This allows Friendica to display the smaller remote image if
  514. // one exists, while still linking to the full-size image
  515. if ($scale_replace) {
  516. $scaled = str_replace($scale_replace[0], $scale_replace[1], $mtch[1]);
  517. } else {
  518. $scaled = $mtch[1];
  519. }
  520. $i = fetch_url($scaled);
  521. if (! $i) {
  522. return $srctext;
  523. }
  524. // guess mimetype from headers or filename
  525. $type = guess_image_type($mtch[1],true);
  526. if ($i) {
  527. $ph = new Photo($i, $type);
  528. if ($ph->is_valid()) {
  529. $orig_width = $ph->getWidth();
  530. $orig_height = $ph->getHeight();
  531. if ($orig_width > 640 || $orig_height > 640) {
  532. $ph->scaleImage(640);
  533. $new_width = $ph->getWidth();
  534. $new_height = $ph->getHeight();
  535. logger('scale_external_images: ' . $orig_width . '->' . $new_width . 'w ' . $orig_height . '->' . $new_height . 'h' . ' match: ' . $mtch[0], LOGGER_DEBUG);
  536. $s = str_replace($mtch[0],'[img=' . $new_width . 'x' . $new_height. ']' . $scaled . '[/img]'
  537. . "\n" . (($include_link)
  538. ? '[url=' . $mtch[1] . ']' . t('view full size') . '[/url]' . "\n"
  539. : ''),$s);
  540. logger('scale_external_images: new string: ' . $s, LOGGER_DEBUG);
  541. }
  542. }
  543. }
  544. }
  545. }
  546. // replace the special char encoding
  547. $s = htmlspecialchars($s,ENT_NOQUOTES,'UTF-8');
  548. return $s;
  549. }
  550. function fix_contact_ssl_policy(&$contact,$new_policy) {
  551. $ssl_changed = false;
  552. if ((intval($new_policy) == SSL_POLICY_SELFSIGN || $new_policy === 'self') && strstr($contact['url'],'https:')) {
  553. $ssl_changed = true;
  554. $contact['url'] = str_replace('https:','http:',$contact['url']);
  555. $contact['request'] = str_replace('https:','http:',$contact['request']);
  556. $contact['notify'] = str_replace('https:','http:',$contact['notify']);
  557. $contact['poll'] = str_replace('https:','http:',$contact['poll']);
  558. $contact['confirm'] = str_replace('https:','http:',$contact['confirm']);
  559. $contact['poco'] = str_replace('https:','http:',$contact['poco']);
  560. }
  561. if ((intval($new_policy) == SSL_POLICY_FULL || $new_policy === 'full') && strstr($contact['url'],'http:')) {
  562. $ssl_changed = true;
  563. $contact['url'] = str_replace('http:','https:',$contact['url']);
  564. $contact['request'] = str_replace('http:','https:',$contact['request']);
  565. $contact['notify'] = str_replace('http:','https:',$contact['notify']);
  566. $contact['poll'] = str_replace('http:','https:',$contact['poll']);
  567. $contact['confirm'] = str_replace('http:','https:',$contact['confirm']);
  568. $contact['poco'] = str_replace('http:','https:',$contact['poco']);
  569. }
  570. if ($ssl_changed) {
  571. q("UPDATE `contact` SET
  572. `url` = '%s',
  573. `request` = '%s',
  574. `notify` = '%s',
  575. `poll` = '%s',
  576. `confirm` = '%s',
  577. `poco` = '%s'
  578. WHERE `id` = %d LIMIT 1",
  579. dbesc($contact['url']),
  580. dbesc($contact['request']),
  581. dbesc($contact['notify']),
  582. dbesc($contact['poll']),
  583. dbesc($contact['confirm']),
  584. dbesc($contact['poco']),
  585. intval($contact['id'])
  586. );
  587. }
  588. }
  589. /**
  590. * @brief Remove Google Analytics and other tracking platforms params from URL
  591. *
  592. * @param string $url Any user-submitted URL that may contain tracking params
  593. * @return string The same URL stripped of tracking parameters
  594. */
  595. function strip_tracking_query_params($url)
  596. {
  597. $urldata = parse_url($url);
  598. if (is_string($urldata["query"])) {
  599. $query = $urldata["query"];
  600. parse_str($query, $querydata);
  601. if (is_array($querydata)) {
  602. foreach ($querydata AS $param => $value) {
  603. if (in_array($param, array("utm_source", "utm_medium", "utm_term", "utm_content", "utm_campaign",
  604. "wt_mc", "pk_campaign", "pk_kwd", "mc_cid", "mc_eid",
  605. "fb_action_ids", "fb_action_types", "fb_ref",
  606. "awesm", "wtrid",
  607. "woo_campaign", "woo_source", "woo_medium", "woo_content", "woo_term"))) {
  608. $pair = $param . "=" . urlencode($value);
  609. $url = str_replace($pair, "", $url);
  610. // Second try: if the url isn't encoded completely
  611. $pair = $param . "=" . str_replace(" ", "+", $value);
  612. $url = str_replace($pair, "", $url);
  613. // Third try: Maybey the url isn't encoded at all
  614. $pair = $param . "=" . $value;
  615. $url = str_replace($pair, "", $url);
  616. $url = str_replace(array("?&", "&&"), array("?", ""), $url);
  617. }
  618. }
  619. }
  620. if (substr($url, -1, 1) == "?") {
  621. $url = substr($url, 0, -1);
  622. }
  623. }
  624. return $url;
  625. }
  626. /**
  627. * @brief Returns the original URL of the provided URL
  628. *
  629. * This function strips tracking query params and follows redirections, either
  630. * through HTTP code or meta refresh tags. Stops after 10 redirections.
  631. *
  632. * @todo Remove the $fetchbody parameter that generates an extraneous HEAD request
  633. *
  634. * @see ParseUrl::getSiteinfo
  635. *
  636. * @param string $url A user-submitted URL
  637. * @param int $depth The current redirection recursion level (internal)
  638. * @param bool $fetchbody Wether to fetch the body or not after the HEAD requests
  639. * @return string A canonical URL
  640. */
  641. function original_url($url, $depth = 1, $fetchbody = false) {
  642. $a = get_app();
  643. $url = strip_tracking_query_params($url);
  644. if ($depth > 10)
  645. return($url);
  646. $url = trim($url, "'");
  647. $stamp1 = microtime(true);
  648. $siteinfo = array();
  649. $ch = curl_init();
  650. curl_setopt($ch, CURLOPT_URL, $url);
  651. curl_setopt($ch, CURLOPT_HEADER, 1);
  652. curl_setopt($ch, CURLOPT_NOBODY, 1);
  653. curl_setopt($ch, CURLOPT_TIMEOUT, 10);
  654. curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  655. curl_setopt($ch, CURLOPT_USERAGENT, $a->get_useragent());
  656. $header = curl_exec($ch);
  657. $curl_info = @curl_getinfo($ch);
  658. $http_code = $curl_info['http_code'];
  659. curl_close($ch);
  660. $a->save_timestamp($stamp1, "network");
  661. if ($http_code == 0)
  662. return($url);
  663. if ((($curl_info['http_code'] == "301") OR ($curl_info['http_code'] == "302"))
  664. AND (($curl_info['redirect_url'] != "") OR ($curl_info['location'] != ""))) {
  665. if ($curl_info['redirect_url'] != "")
  666. return(original_url($curl_info['redirect_url'], ++$depth, $fetchbody));
  667. else
  668. return(original_url($curl_info['location'], ++$depth, $fetchbody));
  669. }
  670. // Check for redirects in the meta elements of the body if there are no redirects in the header.
  671. if (!$fetchbody)
  672. return(original_url($url, ++$depth, true));
  673. // if the file is too large then exit
  674. if ($curl_info["download_content_length"] > 1000000)
  675. return($url);
  676. // if it isn't a HTML file then exit
  677. if (($curl_info["content_type"] != "") AND !strstr(strtolower($curl_info["content_type"]),"html"))
  678. return($url);
  679. $stamp1 = microtime(true);
  680. $ch = curl_init();
  681. curl_setopt($ch, CURLOPT_URL, $url);
  682. curl_setopt($ch, CURLOPT_HEADER, 0);
  683. curl_setopt($ch, CURLOPT_NOBODY, 0);
  684. curl_setopt($ch, CURLOPT_TIMEOUT, 10);
  685. curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  686. curl_setopt($ch, CURLOPT_USERAGENT, $a->get_useragent());
  687. $body = curl_exec($ch);
  688. curl_close($ch);
  689. $a->save_timestamp($stamp1, "network");
  690. if (trim($body) == "")
  691. return($url);
  692. // Check for redirect in meta elements
  693. $doc = new DOMDocument();
  694. @$doc->loadHTML($body);
  695. $xpath = new DomXPath($doc);
  696. $list = $xpath->query("//meta[@content]");
  697. foreach ($list as $node) {
  698. $attr = array();
  699. if ($node->attributes->length)
  700. foreach ($node->attributes as $attribute)
  701. $attr[$attribute->name] = $attribute->value;
  702. if (@$attr["http-equiv"] == 'refresh') {
  703. $path = $attr["content"];
  704. $pathinfo = explode(";", $path);
  705. $content = "";
  706. foreach ($pathinfo AS $value)
  707. if (substr(strtolower($value), 0, 4) == "url=")
  708. return(original_url(substr($value, 4), ++$depth));
  709. }
  710. }
  711. return($url);
  712. }
  713. function short_link($url) {
  714. require_once('library/slinky.php');
  715. $slinky = new Slinky($url);
  716. $yourls_url = get_config('yourls','url1');
  717. if ($yourls_url) {
  718. $yourls_username = get_config('yourls','username1');
  719. $yourls_password = get_config('yourls', 'password1');
  720. $yourls_ssl = get_config('yourls', 'ssl1');
  721. $yourls = new Slinky_YourLS();
  722. $yourls->set('username', $yourls_username);
  723. $yourls->set('password', $yourls_password);
  724. $yourls->set('ssl', $yourls_ssl);
  725. $yourls->set('yourls-url', $yourls_url);
  726. $slinky->set_cascade(array($yourls, new Slinky_Ur1ca(), new Slinky_TinyURL()));
  727. } else {
  728. // setup a cascade of shortening services
  729. // try to get a short link from these services
  730. // in the order ur1.ca, tinyurl
  731. $slinky->set_cascade(array(new Slinky_Ur1ca(), new Slinky_TinyURL()));
  732. }
  733. return $slinky->short();
  734. }
  735. /**
  736. * @brief Encodes content to json
  737. *
  738. * This function encodes an array to json format
  739. * and adds an application/json HTTP header to the output.
  740. * After finishing the process is getting killed.
  741. *
  742. * @param array $x The input content
  743. */
  744. function json_return_and_die($x) {
  745. header("content-type: application/json");
  746. echo json_encode($x);
  747. killme();
  748. }
  749. /**
  750. * @brief Find the matching part between two url
  751. *
  752. * @param string $url1
  753. * @param string $url2
  754. * @return string The matching part
  755. */
  756. function matching_url($url1, $url2) {
  757. if (($url1 == "") OR ($url2 == ""))
  758. return "";
  759. $url1 = normalise_link($url1);
  760. $url2 = normalise_link($url2);
  761. $parts1 = parse_url($url1);
  762. $parts2 = parse_url($url2);
  763. if (!isset($parts1["host"]) OR !isset($parts2["host"]))
  764. return "";
  765. if ($parts1["scheme"] != $parts2["scheme"])
  766. return "";
  767. if ($parts1["host"] != $parts2["host"])
  768. return "";
  769. if ($parts1["port"] != $parts2["port"])
  770. return "";
  771. $match = $parts1["scheme"]."://".$parts1["host"];
  772. if ($parts1["port"])
  773. $match .= ":".$parts1["port"];
  774. $pathparts1 = explode("/", $parts1["path"]);
  775. $pathparts2 = explode("/", $parts2["path"]);
  776. $i = 0;
  777. $path = "";
  778. do {
  779. $path1 = $pathparts1[$i];
  780. $path2 = $pathparts2[$i];
  781. if ($path1 == $path2)
  782. $path .= $path1."/";
  783. } while (($path1 == $path2) AND ($i++ <= count($pathparts1)));
  784. $match .= $path;
  785. return normalise_link($match);
  786. }