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.

717 lines
18 KiB

10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
  1. <?php
  2. // curl wrapper. If binary flag is true, return binary
  3. // results.
  4. if(! function_exists('fetch_url')) {
  5. function fetch_url($url,$binary = false, &$redirects = 0, $timeout = 0) {
  6. $a = get_app();
  7. $ch = curl_init($url);
  8. if(($redirects > 8) || (! $ch))
  9. return false;
  10. curl_setopt($ch, CURLOPT_HEADER, true);
  11. curl_setopt($ch, CURLOPT_RETURNTRANSFER,true);
  12. curl_setopt($ch, CURLOPT_USERAGENT, "Friendika");
  13. if(intval($timeout)) {
  14. curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
  15. }
  16. else {
  17. $curl_time = intval(get_config('system','curl_timeout'));
  18. curl_setopt($ch, CURLOPT_TIMEOUT, (($curl_time !== false) ? $curl_time : 60));
  19. }
  20. // by default we will allow self-signed certs
  21. // but you can override this
  22. $check_cert = get_config('system','verifyssl');
  23. curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, (($check_cert) ? true : false));
  24. $prx = get_config('system','proxy');
  25. if(strlen($prx)) {
  26. curl_setopt($ch, CURLOPT_HTTPPROXYTUNNEL, 1);
  27. curl_setopt($ch, CURLOPT_PROXY, $prx);
  28. $prxusr = get_config('system','proxyuser');
  29. if(strlen($prxusr))
  30. curl_setopt($ch, CURLOPT_PROXYUSERPWD, $prxusr);
  31. }
  32. if($binary)
  33. curl_setopt($ch, CURLOPT_BINARYTRANSFER,1);
  34. $a->set_curl_code(0);
  35. // don't let curl abort the entire application
  36. // if it throws any errors.
  37. $s = @curl_exec($ch);
  38. $base = $s;
  39. $curl_info = curl_getinfo($ch);
  40. $http_code = $curl_info['http_code'];
  41. $header = '';
  42. // Pull out multiple headers, e.g. proxy and continuation headers
  43. // allow for HTTP/2.x without fixing code
  44. while(preg_match('/^HTTP\/[1-2].+? [1-5][0-9][0-9]/',$base)) {
  45. $chunk = substr($base,0,strpos($base,"\r\n\r\n")+4);
  46. $header .= $chunk;
  47. $base = substr($base,strlen($chunk));
  48. }
  49. if($http_code == 301 || $http_code == 302 || $http_code == 303 || $http_code == 307) {
  50. $matches = array();
  51. preg_match('/(Location:|URI:)(.*?)\n/', $header, $matches);
  52. $url = trim(array_pop($matches));
  53. $url_parsed = @parse_url($url);
  54. if (isset($url_parsed)) {
  55. $redirects++;
  56. return fetch_url($url,$binary,$redirects,$timeout);
  57. }
  58. }
  59. $a->set_curl_code($http_code);
  60. $body = substr($s,strlen($header));
  61. $a->set_curl_headers($header);
  62. curl_close($ch);
  63. return($body);
  64. }}
  65. // post request to $url. $params is an array of post variables.
  66. if(! function_exists('post_url')) {
  67. function post_url($url,$params, $headers = null, &$redirects = 0, $timeout = 0) {
  68. $a = get_app();
  69. $ch = curl_init($url);
  70. if(($redirects > 8) || (! $ch))
  71. return false;
  72. curl_setopt($ch, CURLOPT_HEADER, true);
  73. curl_setopt($ch, CURLOPT_RETURNTRANSFER,true);
  74. curl_setopt($ch, CURLOPT_POST,1);
  75. curl_setopt($ch, CURLOPT_POSTFIELDS,$params);
  76. curl_setopt($ch, CURLOPT_USERAGENT, "Friendika");
  77. if(intval($timeout)) {
  78. curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
  79. }
  80. else {
  81. $curl_time = intval(get_config('system','curl_timeout'));
  82. curl_setopt($ch, CURLOPT_TIMEOUT, (($curl_time !== false) ? $curl_time : 60));
  83. }
  84. if(defined('LIGHTTPD')) {
  85. if(!is_array($headers)) {
  86. $headers = array('Expect:');
  87. } else {
  88. if(!in_array('Expect:', $headers)) {
  89. array_push($headers, 'Expect:');
  90. }
  91. }
  92. }
  93. if($headers)
  94. curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
  95. $check_cert = get_config('system','verifyssl');
  96. curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, (($check_cert) ? true : false));
  97. $prx = get_config('system','proxy');
  98. if(strlen($prx)) {
  99. curl_setopt($ch, CURLOPT_HTTPPROXYTUNNEL, 1);
  100. curl_setopt($ch, CURLOPT_PROXY, $prx);
  101. $prxusr = get_config('system','proxyuser');
  102. if(strlen($prxusr))
  103. curl_setopt($ch, CURLOPT_PROXYUSERPWD, $prxusr);
  104. }
  105. $a->set_curl_code(0);
  106. // don't let curl abort the entire application
  107. // if it throws any errors.
  108. $s = @curl_exec($ch);
  109. $base = $s;
  110. $curl_info = curl_getinfo($ch);
  111. $http_code = $curl_info['http_code'];
  112. $header = '';
  113. // Pull out multiple headers, e.g. proxy and continuation headers
  114. // allow for HTTP/2.x without fixing code
  115. while(preg_match('/^HTTP\/[1-2].+? [1-5][0-9][0-9]/',$base)) {
  116. $chunk = substr($base,0,strpos($base,"\r\n\r\n")+4);
  117. $header .= $chunk;
  118. $base = substr($base,strlen($chunk));
  119. }
  120. if($http_code == 301 || $http_code == 302 || $http_code == 303) {
  121. $matches = array();
  122. preg_match('/(Location:|URI:)(.*?)\n/', $header, $matches);
  123. $url = trim(array_pop($matches));
  124. $url_parsed = @parse_url($url);
  125. if (isset($url_parsed)) {
  126. $redirects++;
  127. return post_url($url,$params,$headers,$redirects,$timeout);
  128. }
  129. }
  130. $a->set_curl_code($http_code);
  131. $body = substr($s,strlen($header));
  132. $a->set_curl_headers($header);
  133. curl_close($ch);
  134. return($body);
  135. }}
  136. // Generic XML return
  137. // Outputs a basic dfrn XML status structure to STDOUT, with a <status> variable
  138. // of $st and an optional text <message> of $message and terminates the current process.
  139. if(! function_exists('xml_status')) {
  140. function xml_status($st, $message = '') {
  141. $xml_message = ((strlen($message)) ? "\t<message>" . xmlify($message) . "</message>\r\n" : '');
  142. if($st)
  143. logger('xml_status returning non_zero: ' . $st . " message=" . $message);
  144. header( "Content-type: text/xml" );
  145. echo '<?xml version="1.0" encoding="UTF-8"?>'."\r\n";
  146. echo "<result>\r\n\t<status>$st</status>\r\n$xml_message</result>\r\n";
  147. killme();
  148. }}
  149. if(! function_exists('http_status_exit')) {
  150. function http_status_exit($val) {
  151. if($val >= 400)
  152. $err = 'Error';
  153. if($val >= 200 && $val < 300)
  154. $err = 'OK';
  155. logger('http_status_exit ' . $val);
  156. header($_SERVER["SERVER_PROTOCOL"] . ' ' . $val . ' ' . $err);
  157. killme();
  158. }}
  159. // convert an XML document to a normalised, case-corrected array
  160. // used by webfinger
  161. if(! function_exists('convert_xml_element_to_array')) {
  162. function convert_xml_element_to_array($xml_element, &$recursion_depth=0) {
  163. // If we're getting too deep, bail out
  164. if ($recursion_depth > 512) {
  165. return(null);
  166. }
  167. if (!is_string($xml_element) &&
  168. !is_array($xml_element) &&
  169. (get_class($xml_element) == 'SimpleXMLElement')) {
  170. $xml_element_copy = $xml_element;
  171. $xml_element = get_object_vars($xml_element);
  172. }
  173. if (is_array($xml_element)) {
  174. $result_array = array();
  175. if (count($xml_element) <= 0) {
  176. return (trim(strval($xml_element_copy)));
  177. }
  178. foreach($xml_element as $key=>$value) {
  179. $recursion_depth++;
  180. $result_array[strtolower($key)] =
  181. convert_xml_element_to_array($value, $recursion_depth);
  182. $recursion_depth--;
  183. }
  184. if ($recursion_depth == 0) {
  185. $temp_array = $result_array;
  186. $result_array = array(
  187. strtolower($xml_element_copy->getName()) => $temp_array,
  188. );
  189. }
  190. return ($result_array);
  191. } else {
  192. return (trim(strval($xml_element)));
  193. }
  194. }}
  195. // Given an email style address, perform webfinger lookup and
  196. // return the resulting DFRN profile URL, or if no DFRN profile URL
  197. // is located, returns an OStatus subscription template (prefixed
  198. // with the string 'stat:' to identify it as on OStatus template).
  199. // If this isn't an email style address just return $s.
  200. // Return an empty string if email-style addresses but webfinger fails,
  201. // or if the resultant personal XRD doesn't contain a supported
  202. // subscription/friend-request attribute.
  203. if(! function_exists('webfinger_dfrn')) {
  204. function webfinger_dfrn($s) {
  205. if(! strstr($s,'@')) {
  206. return $s;
  207. }
  208. $links = webfinger($s);
  209. logger('webfinger_dfrn: ' . $s . ':' . print_r($links,true), LOGGER_DATA);
  210. if(count($links)) {
  211. foreach($links as $link)
  212. if($link['@attributes']['rel'] === NAMESPACE_DFRN)
  213. return $link['@attributes']['href'];
  214. foreach($links as $link)
  215. if($link['@attributes']['rel'] === NAMESPACE_OSTATUSSUB)
  216. return 'stat:' . $link['@attributes']['template'];
  217. }
  218. return '';
  219. }}
  220. // Given an email style address, perform webfinger lookup and
  221. // return the array of link attributes from the personal XRD file.
  222. // On error/failure return an empty array.
  223. if(! function_exists('webfinger')) {
  224. function webfinger($s) {
  225. $host = '';
  226. if(strstr($s,'@')) {
  227. $host = substr($s,strpos($s,'@') + 1);
  228. }
  229. if(strlen($host)) {
  230. $tpl = fetch_lrdd_template($host);
  231. logger('webfinger: lrdd template: ' . $tpl);
  232. if(strlen($tpl)) {
  233. $pxrd = str_replace('{uri}', urlencode('acct:' . $s), $tpl);
  234. logger('webfinger: pxrd: ' . $pxrd);
  235. $links = fetch_xrd_links($pxrd);
  236. if(! count($links)) {
  237. // try with double slashes
  238. $pxrd = str_replace('{uri}', urlencode('acct://' . $s), $tpl);
  239. logger('webfinger: pxrd: ' . $pxrd);
  240. $links = fetch_xrd_links($pxrd);
  241. }
  242. return $links;
  243. }
  244. }
  245. return array();
  246. }}
  247. if(! function_exists('lrdd')) {
  248. function lrdd($uri) {
  249. $a = get_app();
  250. // default priority is host priority, host-meta first
  251. $priority = 'host';
  252. // All we have is an email address. Resource-priority is irrelevant
  253. // because our URI isn't directly resolvable.
  254. if(strstr($uri,'@')) {
  255. return(webfinger($uri));
  256. }
  257. // get the host meta file
  258. $host = @parse_url($uri);
  259. if($host) {
  260. $url = ((x($host,'scheme')) ? $host['scheme'] : 'http') . '://';
  261. $url .= $host['host'] . '/.well-known/host-meta' ;
  262. }
  263. else
  264. return array();
  265. logger('lrdd: constructed url: ' . $url);
  266. $xml = fetch_url($url);
  267. $headers = $a->get_curl_headers();
  268. if (! $xml)
  269. return array();
  270. logger('lrdd: host_meta: ' . $xml, LOGGER_DATA);
  271. $h = parse_xml_string($xml);
  272. if(! $h)
  273. return array();
  274. $arr = convert_xml_element_to_array($h);
  275. if(isset($arr['xrd']['property'])) {
  276. $property = $arr['crd']['property'];
  277. if(! isset($property[0]))
  278. $properties = array($property);
  279. else
  280. $properties = $property;
  281. foreach($properties as $prop)
  282. if((string) $prop['@attributes'] === 'http://lrdd.net/priority/resource')
  283. $priority = 'resource';
  284. }
  285. // save the links in case we need them
  286. $links = array();
  287. if(isset($arr['xrd']['link'])) {
  288. $link = $arr['xrd']['link'];
  289. if(! isset($link[0]))
  290. $links = array($link);
  291. else
  292. $links = $link;
  293. }
  294. // do we have a template or href?
  295. if(count($links)) {
  296. foreach($links as $link) {
  297. if($link['@attributes']['rel'] && attribute_contains($link['@attributes']['rel'],'lrdd')) {
  298. if(x($link['@attributes'],'template'))
  299. $tpl = $link['@attributes']['template'];
  300. elseif(x($link['@attributes'],'href'))
  301. $href = $link['@attributes']['href'];
  302. }
  303. }
  304. }
  305. if((! isset($tpl)) || (! strpos($tpl,'{uri}')))
  306. $tpl = '';
  307. if($priority === 'host') {
  308. if(strlen($tpl))
  309. $pxrd = str_replace('{uri}', urlencode($uri), $tpl);
  310. elseif(isset($href))
  311. $pxrd = $href;
  312. if(isset($pxrd)) {
  313. logger('lrdd: (host priority) pxrd: ' . $pxrd);
  314. $links = fetch_xrd_links($pxrd);
  315. return $links;
  316. }
  317. $lines = explode("\n",$headers);
  318. if(count($lines)) {
  319. foreach($lines as $line) {
  320. if((stristr($line,'link:')) && preg_match('/<([^>].*)>.*rel\=[\'\"]lrdd[\'\"]/',$line,$matches)) {
  321. return(fetch_xrd_links($matches[1]));
  322. break;
  323. }
  324. }
  325. }
  326. }
  327. // priority 'resource'
  328. $html = fetch_url($uri);
  329. $headers = $a->get_curl_headers();
  330. logger('lrdd: headers=' . $headers, LOGGER_DEBUG);
  331. // don't try and parse raw xml as html
  332. if(! strstr($html,'<?xml')) {
  333. require_once('library/HTML5/Parser.php');
  334. $dom = @HTML5_Parser::parse($html);
  335. if($dom) {
  336. $items = $dom->getElementsByTagName('link');
  337. foreach($items as $item) {
  338. $x = $item->getAttribute('rel');
  339. if($x == "lrdd") {
  340. $pagelink = $item->getAttribute('href');
  341. break;
  342. }
  343. }
  344. }
  345. }
  346. if(isset($pagelink))
  347. return(fetch_xrd_links($pagelink));
  348. // next look in HTTP headers
  349. $lines = explode("\n",$headers);
  350. if(count($lines)) {
  351. foreach($lines as $line) {
  352. // TODO alter the following regex to support multiple relations (space separated)
  353. if((stristr($line,'link:')) && preg_match('/<([^>].*)>.*rel\=[\'\"]lrdd[\'\"]/',$line,$matches)) {
  354. $pagelink = $matches[1];
  355. break;
  356. }
  357. // don't try and run feeds through the html5 parser
  358. if(stristr($line,'content-type:') && ((stristr($line,'application/atom+xml')) || (stristr($line,'application/rss+xml'))))
  359. return array();
  360. if(stristr($html,'<rss') || stristr($html,'<feed'))
  361. return array();
  362. }
  363. }
  364. if(isset($pagelink))
  365. return(fetch_xrd_links($pagelink));
  366. // If we haven't found any links, return the host xrd links (which we have already fetched)
  367. if(isset($links))
  368. return $links;
  369. return array();
  370. }}
  371. // Given a host name, locate the LRDD template from that
  372. // host. Returns the LRDD template or an empty string on
  373. // error/failure.
  374. if(! function_exists('fetch_lrdd_template')) {
  375. function fetch_lrdd_template($host) {
  376. $tpl = '';
  377. $url1 = 'https://' . $host . '/.well-known/host-meta' ;
  378. $url2 = 'http://' . $host . '/.well-known/host-meta' ;
  379. $links = fetch_xrd_links($url1);
  380. logger('fetch_lrdd_template from: ' . $url1);
  381. logger('template (https): ' . print_r($links,true));
  382. if(! count($links)) {
  383. logger('fetch_lrdd_template from: ' . $url2);
  384. $links = fetch_xrd_links($url2);
  385. logger('template (http): ' . print_r($links,true));
  386. }
  387. if(count($links)) {
  388. foreach($links as $link)
  389. if($link['@attributes']['rel'] && $link['@attributes']['rel'] === 'lrdd')
  390. $tpl = $link['@attributes']['template'];
  391. }
  392. if(! strpos($tpl,'{uri}'))
  393. $tpl = '';
  394. return $tpl;
  395. }}
  396. // Given a URL, retrieve the page as an XRD document.
  397. // Return an array of links.
  398. // on error/failure return empty array.
  399. if(! function_exists('fetch_xrd_links')) {
  400. function fetch_xrd_links($url) {
  401. $xrd_timeout = intval(get_config('system','xrd_timeout'));
  402. $redirects = 0;
  403. $xml = fetch_url($url,false,$redirects,(($xrd_timeout) ? $xrd_timeout : 20));
  404. logger('fetch_xrd_links: ' . $xml, LOGGER_DATA);
  405. if ((! $xml) || (! stristr($xml,'<xrd')))
  406. return array();
  407. $h = parse_xml_string($xml);
  408. if(! $h)
  409. return array();
  410. $arr = convert_xml_element_to_array($h);
  411. $links = array();
  412. if(isset($arr['xrd']['link'])) {
  413. $link = $arr['xrd']['link'];
  414. if(! isset($link[0]))
  415. $links = array($link);
  416. else
  417. $links = $link;
  418. }
  419. if(isset($arr['xrd']['alias'])) {
  420. $alias = $arr['xrd']['alias'];
  421. if(! isset($alias[0]))
  422. $aliases = array($alias);
  423. else
  424. $aliases = $alias;
  425. if($aliases && count($aliases)) {
  426. foreach($aliases as $alias) {
  427. $links[]['@attributes'] = array('rel' => 'alias' , 'href' => $alias);
  428. }
  429. }
  430. }
  431. logger('fetch_xrd_links: ' . print_r($links,true), LOGGER_DATA);
  432. return $links;
  433. }}
  434. // Take a URL from the wild, prepend http:// if necessary
  435. // and check DNS to see if it's real
  436. // return true if it's OK, false if something is wrong with it
  437. if(! function_exists('validate_url')) {
  438. function validate_url(&$url) {
  439. if(substr($url,0,4) != 'http')
  440. $url = 'http://' . $url;
  441. $h = @parse_url($url);
  442. if(($h) && (dns_get_record($h['host'], DNS_A + DNS_CNAME + DNS_PTR))) {
  443. return true;
  444. }
  445. return false;
  446. }}
  447. // checks that email is an actual resolvable internet address
  448. if(! function_exists('validate_email')) {
  449. function validate_email($addr) {
  450. if(! strpos($addr,'@'))
  451. return false;
  452. $h = substr($addr,strpos($addr,'@') + 1);
  453. if(($h) && (dns_get_record($h, DNS_A + DNS_CNAME + DNS_PTR + DNS_MX))) {
  454. return true;
  455. }
  456. return false;
  457. }}
  458. // Check $url against our list of allowed sites,
  459. // wildcards allowed. If allowed_sites is unset return true;
  460. // If url is allowed, return true.
  461. // otherwise, return false
  462. if(! function_exists('allowed_url')) {
  463. function allowed_url($url) {
  464. $h = @parse_url($url);
  465. if(! $h) {
  466. return false;
  467. }
  468. $str_allowed = get_config('system','allowed_sites');
  469. if(! $str_allowed)
  470. return true;
  471. $found = false;
  472. $host = strtolower($h['host']);
  473. // always allow our own site
  474. if($host == strtolower($_SERVER['SERVER_NAME']))
  475. return true;
  476. $fnmatch = function_exists('fnmatch');
  477. $allowed = explode(',',$str_allowed);
  478. if(count($allowed)) {
  479. foreach($allowed as $a) {
  480. $pat = strtolower(trim($a));
  481. if(($fnmatch && fnmatch($pat,$host)) || ($pat == $host)) {
  482. $found = true;
  483. break;
  484. }
  485. }
  486. }
  487. return $found;
  488. }}
  489. // check if email address is allowed to register here.
  490. // Compare against our list (wildcards allowed).
  491. // Returns false if not allowed, true if allowed or if
  492. // allowed list is not configured.
  493. if(! function_exists('allowed_email')) {
  494. function allowed_email($email) {
  495. $domain = strtolower(substr($email,strpos($email,'@') + 1));
  496. if(! $domain)
  497. return false;
  498. $str_allowed = get_config('system','allowed_email');
  499. if(! $str_allowed)
  500. return true;
  501. $found = false;
  502. $fnmatch = function_exists('fnmatch');
  503. $allowed = explode(',',$str_allowed);
  504. if(count($allowed)) {
  505. foreach($allowed as $a) {
  506. $pat = strtolower(trim($a));
  507. if(($fnmatch && fnmatch($pat,$domain)) || ($pat == $domain)) {
  508. $found = true;
  509. break;
  510. }
  511. }
  512. }
  513. return $found;
  514. }}
  515. if(! function_exists('gravatar_img')) {
  516. function gravatar_img($email) {
  517. $size = 175;
  518. $opt = 'identicon'; // psuedo-random geometric pattern if not found
  519. $rating = 'pg';
  520. $hash = md5(trim(strtolower($email)));
  521. $url = 'http://www.gravatar.com/avatar/' . $hash . '.jpg'
  522. . '?s=' . $size . '&d=' . $opt . '&r=' . $rating;
  523. logger('gravatar: ' . $email . ' ' . $url);
  524. return $url;
  525. }}
  526. if(! function_exists('parse_xml_string')) {
  527. function parse_xml_string($s,$strict = true) {
  528. if($strict) {
  529. if(! strstr($s,'<?xml'))
  530. return false;
  531. $s2 = substr($s,strpos($s,'<?xml'));
  532. }
  533. else
  534. $s2 = $s;
  535. libxml_use_internal_errors(true);
  536. $x = @simplexml_load_string($s2);
  537. if(! $x) {
  538. logger('libxml: parse: error: ' . $s2, LOGGER_DATA);
  539. foreach(libxml_get_errors() as $err)
  540. logger('libxml: parse: ' . $err->code." at ".$err->line.":".$err->column." : ".$err->message, LOGGER_DATA);
  541. libxml_clear_errors();
  542. }
  543. return $x;
  544. }}
  545. function add_fcontact($arr) {
  546. $r = q("insert into fcontact ( `url`,`name`,`photo`,`request`,`nick`,`addr`,
  547. `notify`,`poll`,`confirm`,`network`,`alias`,`pubkey`,`updated` )
  548. values('%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s')",
  549. dbesc($arr['url']),
  550. dbesc($arr['name']),
  551. dbesc($arr['photo']),
  552. dbesc($arr['request']),
  553. dbesc($arr['nick']),
  554. dbesc($arr['addr']),
  555. dbesc($arr['notify']),
  556. dbesc($arr['poll']),
  557. dbesc($arr['confirm']),
  558. dbesc($arr['network']),
  559. dbesc($arr['alias']),
  560. dbesc($arr['pubkey']),
  561. dbesc(datetime_convert())
  562. );
  563. return $r;
  564. }