]> git.mxchange.org Git - friendica.git/blob - include/network.php
The global contact for server users is now stored directly
[friendica.git] / include / network.php
1 <?php
2
3 /**
4  * @file include/network.php
5  */
6
7 require_once("include/xml.php");
8
9
10 /**
11  * @brief Curl wrapper
12  * 
13  * If binary flag is true, return binary results.
14  * Set the cookiejar argument to a string (e.g. "/tmp/friendica-cookies.txt")
15  * to preserve cookies from one request to the next.
16  * 
17  * @param string $url URL to fetch
18  * @param boolean $binary default false
19  *    TRUE if asked to return binary results (file download)
20  * @param integer $redirects The recursion counter for internal use - default 0
21  * @param integer $timeout Timeout in seconds, default system config value or 60 seconds
22  * @param string $accept_content supply Accept: header with 'accept_content' as the value
23  * @param string $cookiejar Path to cookie jar file
24  * 
25  * @return string The fetched content
26  */
27 function fetch_url($url,$binary = false, &$redirects = 0, $timeout = 0, $accept_content=Null, $cookiejar = 0) {
28
29         $ret = z_fetch_url(
30                 $url,
31                 $binary,
32                 $redirects,
33                 array('timeout'=>$timeout,
34                 'accept_content'=>$accept_content,
35                 'cookiejar'=>$cookiejar
36                 ));
37
38         return($ret['body']);
39 }
40
41 /**
42  * @brief fetches an URL.
43  *
44  * @param string $url URL to fetch
45  * @param boolean $binary default false
46  *    TRUE if asked to return binary results (file download)
47  * @param int $redirects The recursion counter for internal use - default 0
48  * @param array $opts (optional parameters) assoziative array with:
49  *    'accept_content' => supply Accept: header with 'accept_content' as the value
50  *    'timeout' => int Timeout in seconds, default system config value or 60 seconds
51  *    'http_auth' => username:password
52  *    'novalidate' => do not validate SSL certs, default is to validate using our CA list
53  *    'nobody' => only return the header
54  *    'cookiejar' => path to cookie jar file
55  *
56  * @return array an assoziative array with:
57  *    int 'return_code' => HTTP return code or 0 if timeout or failure
58  *    boolean 'success' => boolean true (if HTTP 2xx result) or false
59  *    string 'redirect_url' => in case of redirect, content was finally retrieved from this URL
60  *    string 'header' => HTTP headers
61  *    string 'body' => fetched content
62  */
63 function z_fetch_url($url,$binary = false, &$redirects = 0, $opts=array()) {
64
65         $ret = array('return_code' => 0, 'success' => false, 'header' => "", 'body' => "");
66
67
68         $stamp1 = microtime(true);
69
70         $a = get_app();
71
72         $ch = @curl_init($url);
73         if(($redirects > 8) || (! $ch))
74                 return false;
75
76         @curl_setopt($ch, CURLOPT_HEADER, true);
77
78         if(x($opts,"cookiejar")) {
79                 curl_setopt($ch, CURLOPT_COOKIEJAR, $opts["cookiejar"]);
80                 curl_setopt($ch, CURLOPT_COOKIEFILE, $opts["cookiejar"]);
81         }
82
83 // These settings aren't needed. We're following the location already.
84 //      @curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
85 //      @curl_setopt($ch, CURLOPT_MAXREDIRS, 5);
86
87         if (x($opts,'accept_content')){
88                 curl_setopt($ch,CURLOPT_HTTPHEADER, array (
89                         "Accept: " . $opts['accept_content']
90                 ));
91         }
92
93         @curl_setopt($ch, CURLOPT_RETURNTRANSFER,true);
94         @curl_setopt($ch, CURLOPT_USERAGENT, $a->get_useragent());
95
96
97
98         if(x($opts,'headers')){
99                 @curl_setopt($ch, CURLOPT_HTTPHEADER, $opts['headers']);
100         }
101         if(x($opts,'nobody')){
102                 @curl_setopt($ch, CURLOPT_NOBODY, $opts['nobody']);
103         }
104         if(x($opts,'timeout')){
105                 @curl_setopt($ch, CURLOPT_TIMEOUT, $opts['timeout']);
106         } else {
107                 $curl_time = intval(get_config('system','curl_timeout'));
108                 @curl_setopt($ch, CURLOPT_TIMEOUT, (($curl_time !== false) ? $curl_time : 60));
109         }
110
111         // by default we will allow self-signed certs
112         // but you can override this
113
114         $check_cert = get_config('system','verifyssl');
115         @curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, (($check_cert) ? true : false));
116         @curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, (($check_cert) ? 2 : false));
117
118         $prx = get_config('system','proxy');
119         if(strlen($prx)) {
120                 @curl_setopt($ch, CURLOPT_HTTPPROXYTUNNEL, 1);
121                 @curl_setopt($ch, CURLOPT_PROXY, $prx);
122                 $prxusr = @get_config('system','proxyuser');
123                 if(strlen($prxusr))
124                         @curl_setopt($ch, CURLOPT_PROXYUSERPWD, $prxusr);
125         }
126         if($binary)
127                 @curl_setopt($ch, CURLOPT_BINARYTRANSFER,1);
128
129         $a->set_curl_code(0);
130
131         // don't let curl abort the entire application
132         // if it throws any errors.
133
134         $s = @curl_exec($ch);
135         if (curl_errno($ch) !== CURLE_OK) {
136                 logger('fetch_url error fetching '.$url.': '.curl_error($ch), LOGGER_NORMAL);
137         }
138
139         $base = $s;
140         $curl_info = @curl_getinfo($ch);
141
142         $http_code = $curl_info['http_code'];
143         logger('fetch_url '.$url.': '.$http_code." ".$s, LOGGER_DATA);
144         $header = '';
145
146         // Pull out multiple headers, e.g. proxy and continuation headers
147         // allow for HTTP/2.x without fixing code
148
149         while(preg_match('/^HTTP\/[1-2].+? [1-5][0-9][0-9]/',$base)) {
150                 $chunk = substr($base,0,strpos($base,"\r\n\r\n")+4);
151                 $header .= $chunk;
152                 $base = substr($base,strlen($chunk));
153         }
154
155         $a->set_curl_code($http_code);
156         $a->set_curl_content_type($curl_info['content_type']);
157         $a->set_curl_headers($header);
158
159         if($http_code == 301 || $http_code == 302 || $http_code == 303 || $http_code == 307) {
160                 $new_location_info = @parse_url($curl_info["redirect_url"]);
161                 $old_location_info = @parse_url($curl_info["url"]);
162
163                 $newurl = $curl_info["redirect_url"];
164
165                 if (($new_location_info["path"] == "") AND ($new_location_info["host"] != ""))
166                         $newurl = $new_location_info["scheme"]."://".$new_location_info["host"].$old_location_info["path"];
167
168                 $matches = array();
169                 if (preg_match('/(Location:|URI:)(.*?)\n/i', $header, $matches)) {
170                         $newurl = trim(array_pop($matches));
171                 }
172                 if(strpos($newurl,'/') === 0)
173                         $newurl = $old_location_info["scheme"]."://".$old_location_info["host"].$newurl;
174                 if (filter_var($newurl, FILTER_VALIDATE_URL)) {
175                         $redirects++;
176                         @curl_close($ch);
177                         return z_fetch_url($newurl,$binary, $redirects, $opts);
178                 }
179         }
180
181
182         $a->set_curl_code($http_code);
183         $a->set_curl_content_type($curl_info['content_type']);
184
185         $body = substr($s,strlen($header));
186
187
188
189         $rc = intval($http_code);
190         $ret['return_code'] = $rc;
191         $ret['success'] = (($rc >= 200 && $rc <= 299) ? true : false);
192         $ret['redirect_url'] = $url;
193         if(! $ret['success']) {
194                 $ret['error'] = curl_error($ch);
195                 $ret['debug'] = $curl_info;
196                 logger('z_fetch_url: error: ' . $url . ': ' . $ret['error'], LOGGER_DEBUG);
197                 logger('z_fetch_url: debug: ' . print_r($curl_info,true), LOGGER_DATA);
198         }
199         $ret['body'] = substr($s,strlen($header));
200         $ret['header'] = $header;
201         if(x($opts,'debug')) {
202                 $ret['debug'] = $curl_info;
203         }
204         @curl_close($ch);
205
206         $a->save_timestamp($stamp1, "network");
207
208         return($ret);
209
210 }
211
212 // post request to $url. $params is an array of post variables.
213
214 /**
215  * @brief Post request to $url
216  * 
217  * @param string $url URL to post
218  * @param mixed $params
219  * @param string $headers HTTP headers
220  * @param integer $redirects Recursion counter for internal use - default = 0
221  * @param integer $timeout The timeout in seconds, default system config value or 60 seconds
222  * 
223  * @return string The content
224  */
225 function post_url($url,$params, $headers = null, &$redirects = 0, $timeout = 0) {
226         $stamp1 = microtime(true);
227
228         $a = get_app();
229         $ch = curl_init($url);
230         if(($redirects > 8) || (! $ch))
231                 return false;
232
233         logger("post_url: start ".$url, LOGGER_DATA);
234
235         curl_setopt($ch, CURLOPT_HEADER, true);
236         curl_setopt($ch, CURLOPT_RETURNTRANSFER,true);
237         curl_setopt($ch, CURLOPT_POST,1);
238         curl_setopt($ch, CURLOPT_POSTFIELDS,$params);
239         curl_setopt($ch, CURLOPT_USERAGENT, $a->get_useragent());
240
241         if(intval($timeout)) {
242                 curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
243         }
244         else {
245                 $curl_time = intval(get_config('system','curl_timeout'));
246                 curl_setopt($ch, CURLOPT_TIMEOUT, (($curl_time !== false) ? $curl_time : 60));
247         }
248
249         if(defined('LIGHTTPD')) {
250                 if(!is_array($headers)) {
251                         $headers = array('Expect:');
252                 } else {
253                         if(!in_array('Expect:', $headers)) {
254                                 array_push($headers, 'Expect:');
255                         }
256                 }
257         }
258         if($headers)
259                 curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
260
261         $check_cert = get_config('system','verifyssl');
262         curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, (($check_cert) ? true : false));
263         curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, (($check_cert) ? 2 : false));
264         $prx = get_config('system','proxy');
265         if(strlen($prx)) {
266                 curl_setopt($ch, CURLOPT_HTTPPROXYTUNNEL, 1);
267                 curl_setopt($ch, CURLOPT_PROXY, $prx);
268                 $prxusr = get_config('system','proxyuser');
269                 if(strlen($prxusr))
270                         curl_setopt($ch, CURLOPT_PROXYUSERPWD, $prxusr);
271         }
272
273         $a->set_curl_code(0);
274
275         // don't let curl abort the entire application
276         // if it throws any errors.
277
278         $s = @curl_exec($ch);
279
280         $base = $s;
281         $curl_info = curl_getinfo($ch);
282         $http_code = $curl_info['http_code'];
283
284         logger("post_url: result ".$http_code." - ".$url, LOGGER_DATA);
285
286         $header = '';
287
288         // Pull out multiple headers, e.g. proxy and continuation headers
289         // allow for HTTP/2.x without fixing code
290
291         while(preg_match('/^HTTP\/[1-2].+? [1-5][0-9][0-9]/',$base)) {
292                 $chunk = substr($base,0,strpos($base,"\r\n\r\n")+4);
293                 $header .= $chunk;
294                 $base = substr($base,strlen($chunk));
295         }
296
297         if($http_code == 301 || $http_code == 302 || $http_code == 303 || $http_code == 307) {
298                 $matches = array();
299                 preg_match('/(Location:|URI:)(.*?)\n/', $header, $matches);
300                 $newurl = trim(array_pop($matches));
301                 if(strpos($newurl,'/') === 0)
302                         $newurl = $old_location_info["scheme"] . "://" . $old_location_info["host"] . $newurl;
303                 if (filter_var($newurl, FILTER_VALIDATE_URL)) {
304                         $redirects++;
305                         logger("post_url: redirect ".$url." to ".$newurl);
306                         return post_url($newurl,$params, $headers, $redirects, $timeout);
307                         //return fetch_url($newurl,false,$redirects,$timeout);
308                 }
309         }
310         $a->set_curl_code($http_code);
311         $body = substr($s,strlen($header));
312
313         $a->set_curl_headers($header);
314
315         curl_close($ch);
316
317         $a->save_timestamp($stamp1, "network");
318
319         logger("post_url: end ".$url, LOGGER_DATA);
320
321         return($body);
322 }
323
324 // Generic XML return
325 // Outputs a basic dfrn XML status structure to STDOUT, with a <status> variable
326 // of $st and an optional text <message> of $message and terminates the current process.
327
328 function xml_status($st, $message = '') {
329
330         $xml_message = ((strlen($message)) ? "\t<message>" . xmlify($message) . "</message>\r\n" : '');
331
332         if($st)
333                 logger('xml_status returning non_zero: ' . $st . " message=" . $message);
334
335         header( "Content-type: text/xml" );
336         echo '<?xml version="1.0" encoding="UTF-8"?>'."\r\n";
337         echo "<result>\r\n\t<status>$st</status>\r\n$xml_message</result>\r\n";
338         killme();
339 }
340
341
342 /**
343  * @brief Send HTTP status header and exit.
344  *
345  * @param integer $val HTTP status result value
346  * @param array $description optional message
347  *    'title' => header title
348  *    'description' => optional message
349  */
350
351 function http_status_exit($val, $description = array()) {
352         $err = '';
353         if($val >= 400) {
354                 $err = 'Error';
355                 if (!isset($description["title"]))
356                         $description["title"] = $err." ".$val;
357         }
358         if($val >= 200 && $val < 300)
359                 $err = 'OK';
360
361         logger('http_status_exit ' . $val);
362         header($_SERVER["SERVER_PROTOCOL"] . ' ' . $val . ' ' . $err);
363
364         if (isset($description["title"])) {
365                 $tpl = get_markup_template('http_status.tpl');
366                 echo replace_macros($tpl, array('$title' => $description["title"],
367                                                 '$description' => $description["description"]));
368         }
369
370         killme();
371
372 }
373
374 // Given an email style address, perform webfinger lookup and
375 // return the resulting DFRN profile URL, or if no DFRN profile URL
376 // is located, returns an OStatus subscription template (prefixed
377 // with the string 'stat:' to identify it as on OStatus template).
378 // If this isn't an email style address just return $webbie.
379 // Return an empty string if email-style addresses but webfinger fails,
380 // or if the resultant personal XRD doesn't contain a supported
381 // subscription/friend-request attribute.
382
383 // amended 7/9/2011 to return an hcard which could save potentially loading
384 // a lengthy content page to scrape dfrn attributes
385
386 function webfinger_dfrn($webbie,&$hcard) {
387         if(! strstr($webbie,'@')) {
388                 return $webbie;
389         }
390         $profile_link = '';
391
392         $links = webfinger($webbie);
393         logger('webfinger_dfrn: ' . $webbie . ':' . print_r($links,true), LOGGER_DATA);
394         if(count($links)) {
395                 foreach($links as $link) {
396                         if($link['@attributes']['rel'] === NAMESPACE_DFRN)
397                                 $profile_link = $link['@attributes']['href'];
398                         if($link['@attributes']['rel'] === NAMESPACE_OSTATUSSUB)
399                                 $profile_link = 'stat:' . $link['@attributes']['template'];
400                         if($link['@attributes']['rel'] === 'http://microformats.org/profile/hcard')
401                                 $hcard = $link['@attributes']['href'];
402                 }
403         }
404         return $profile_link;
405 }
406
407 /**
408  * @brief Perform webfinger lookup on an email style address
409  * 
410  * @param string $webbi An email style address
411  * @param boolean $debug
412  * 
413  * @return array of link attributes from the personal XRD file
414  *    empty array on error/failure
415  */
416 function webfinger($webbie, $debug = false) {
417         $host = '';
418         if(strstr($webbie,'@')) {
419                 $host = substr($webbie,strpos($webbie,'@') + 1);
420         }
421         if(strlen($host)) {
422                 $tpl = fetch_lrdd_template($host);
423                 logger('webfinger: lrdd template: ' . $tpl);
424                 if(strlen($tpl)) {
425                         $pxrd = str_replace('{uri}', urlencode('acct:' . $webbie), $tpl);
426                         logger('webfinger: pxrd: ' . $pxrd);
427                         $links = fetch_xrd_links($pxrd);
428                         if(! count($links)) {
429                                 // try with double slashes
430                                 $pxrd = str_replace('{uri}', urlencode('acct://' . $webbie), $tpl);
431                                 logger('webfinger: pxrd: ' . $pxrd);
432                                 $links = fetch_xrd_links($pxrd);
433                         }
434                         return $links;
435                 }
436         }
437         return array();
438 }
439
440 function lrdd($uri, $debug = false) {
441
442         $a = get_app();
443
444         // default priority is host priority, host-meta first
445
446         $priority = 'host';
447
448         // All we have is an email address. Resource-priority is irrelevant
449         // because our URI isn't directly resolvable.
450
451         if(strstr($uri,'@')) {
452                 return(webfinger($uri));
453         }
454
455         // get the host meta file
456
457         $host = @parse_url($uri);
458
459         if($host) {
460                 $url  = ((x($host,'scheme')) ? $host['scheme'] : 'http') . '://';
461                 $url .= $host['host'] . '/.well-known/host-meta' ;
462         }
463         else
464                 return array();
465
466         logger('lrdd: constructed url: ' . $url);
467
468         $xml = fetch_url($url);
469
470         $headers = $a->get_curl_headers();
471
472         if (! $xml)
473                 return array();
474
475         logger('lrdd: host_meta: ' . $xml, LOGGER_DATA);
476
477         if(! stristr($xml,'<xrd'))
478                 return array();
479
480         $h = parse_xml_string($xml);
481         if(! $h)
482                 return array();
483
484         $arr = xml::element_to_array($h);
485
486         if(isset($arr['xrd']['property'])) {
487                 $property = $arr['crd']['property'];
488                 if(! isset($property[0]))
489                         $properties = array($property);
490                 else
491                         $properties = $property;
492                 foreach($properties as $prop)
493                         if((string) $prop['@attributes'] === 'http://lrdd.net/priority/resource')
494                                 $priority = 'resource';
495         }
496
497         // save the links in case we need them
498
499         $links = array();
500
501         if(isset($arr['xrd']['link'])) {
502                 $link = $arr['xrd']['link'];
503                 if(! isset($link[0]))
504                         $links = array($link);
505                 else
506                         $links = $link;
507         }
508
509         // do we have a template or href?
510
511         if(count($links)) {
512                 foreach($links as $link) {
513                         if($link['@attributes']['rel'] && attribute_contains($link['@attributes']['rel'],'lrdd')) {
514                                 if(x($link['@attributes'],'template'))
515                                         $tpl = $link['@attributes']['template'];
516                                 elseif(x($link['@attributes'],'href'))
517                                         $href = $link['@attributes']['href'];
518                         }
519                 }
520         }
521
522         if((! isset($tpl)) || (! strpos($tpl,'{uri}')))
523                 $tpl = '';
524
525         if($priority === 'host') {
526                 if(strlen($tpl))
527                         $pxrd = str_replace('{uri}', urlencode($uri), $tpl);
528                 elseif(isset($href))
529                         $pxrd = $href;
530                 if(isset($pxrd)) {
531                         logger('lrdd: (host priority) pxrd: ' . $pxrd);
532                         $links = fetch_xrd_links($pxrd);
533                         return $links;
534                 }
535
536                 $lines = explode("\n",$headers);
537                 if(count($lines)) {
538                         foreach($lines as $line) {
539                                 if((stristr($line,'link:')) && preg_match('/<([^>].*)>.*rel\=[\'\"]lrdd[\'\"]/',$line,$matches)) {
540                                         return(fetch_xrd_links($matches[1]));
541                                         break;
542                                 }
543                         }
544                 }
545         }
546
547
548         // priority 'resource'
549
550
551         $html = fetch_url($uri);
552         $headers = $a->get_curl_headers();
553         logger('lrdd: headers=' . $headers, LOGGER_DEBUG);
554
555         // don't try and parse raw xml as html
556         if(! strstr($html,'<?xml')) {
557                 require_once('library/HTML5/Parser.php');
558
559                 try {
560                         $dom = HTML5_Parser::parse($html);
561                 } catch (DOMException $e) {
562                         logger('lrdd: parse error: ' . $e);
563                 }
564
565                 if(isset($dom) && $dom) {
566                         $items = $dom->getElementsByTagName('link');
567                         foreach($items as $item) {
568                                 $x = $item->getAttribute('rel');
569                                 if($x == "lrdd") {
570                                         $pagelink = $item->getAttribute('href');
571                                         break;
572                                 }
573                         }
574                 }
575         }
576
577         if(isset($pagelink))
578                 return(fetch_xrd_links($pagelink));
579
580         // next look in HTTP headers
581
582         $lines = explode("\n",$headers);
583         if(count($lines)) {
584                 foreach($lines as $line) {
585                         /// @TODO Alter the following regex to support multiple relations (space separated)
586                         if((stristr($line,'link:')) && preg_match('/<([^>].*)>.*rel\=[\'\"]lrdd[\'\"]/',$line,$matches)) {
587                                 $pagelink = $matches[1];
588                                 break;
589                         }
590                         // don't try and run feeds through the html5 parser
591                         if(stristr($line,'content-type:') && ((stristr($line,'application/atom+xml')) || (stristr($line,'application/rss+xml'))))
592                                 return array();
593                         if(stristr($html,'<rss') || stristr($html,'<feed'))
594                                 return array();
595                 }
596         }
597
598         if(isset($pagelink))
599                 return(fetch_xrd_links($pagelink));
600
601         // If we haven't found any links, return the host xrd links (which we have already fetched)
602
603         if(isset($links))
604                 return $links;
605
606         return array();
607
608 }
609
610 // Given a host name, locate the LRDD template from that
611 // host. Returns the LRDD template or an empty string on
612 // error/failure.
613
614 function fetch_lrdd_template($host) {
615         $tpl = '';
616
617         $url1 = 'https://' . $host . '/.well-known/host-meta' ;
618         $url2 = 'http://' . $host . '/.well-known/host-meta' ;
619         $links = fetch_xrd_links($url1);
620         logger('fetch_lrdd_template from: ' . $url1);
621         logger('template (https): ' . print_r($links,true));
622         if(! count($links)) {
623                 logger('fetch_lrdd_template from: ' . $url2);
624                 $links = fetch_xrd_links($url2);
625                 logger('template (http): ' . print_r($links,true));
626         }
627         if(count($links)) {
628                 foreach($links as $link)
629                         if($link['@attributes']['rel'] && $link['@attributes']['rel'] === 'lrdd' && (!$link['@attributes']['type'] || $link['@attributes']['type'] === 'application/xrd+xml'))
630                                 $tpl = $link['@attributes']['template'];
631         }
632         if(! strpos($tpl,'{uri}'))
633                 $tpl = '';
634         return $tpl;
635 }
636
637 /**
638  * @brief Given a URL, retrieve the page as an XRD document.
639  * 
640  * @param string $url An url
641  * @return array of links
642  *    return empty array on error/failure
643  */
644 function fetch_xrd_links($url) {
645
646         $xrd_timeout = intval(get_config('system','xrd_timeout'));
647         $redirects = 0;
648         $xml = fetch_url($url,false,$redirects,(($xrd_timeout) ? $xrd_timeout : 20), "application/xrd+xml");
649
650         logger('fetch_xrd_links: ' . $xml, LOGGER_DATA);
651
652         if ((! $xml) || (! stristr($xml,'<xrd')))
653                 return array();
654
655         // fix diaspora's bad xml
656         $xml = str_replace(array('href=&quot;','&quot;/>'),array('href="','"/>'),$xml);
657
658         $h = parse_xml_string($xml);
659         if(! $h)
660                 return array();
661
662         $arr = xml::element_to_array($h);
663
664         $links = array();
665
666         if(isset($arr['xrd']['link'])) {
667                 $link = $arr['xrd']['link'];
668                 if(! isset($link[0]))
669                         $links = array($link);
670                 else
671                         $links = $link;
672         }
673         if(isset($arr['xrd']['alias'])) {
674                 $alias = $arr['xrd']['alias'];
675                 if(! isset($alias[0]))
676                         $aliases = array($alias);
677                 else
678                         $aliases = $alias;
679                 if(is_array($aliases) && count($aliases)) {
680                         foreach($aliases as $alias) {
681                                 $links[]['@attributes'] = array('rel' => 'alias' , 'href' => $alias);
682                         }
683                 }
684         }
685
686         logger('fetch_xrd_links: ' . print_r($links,true), LOGGER_DATA);
687
688         return $links;
689
690 }
691
692 /**
693  * @brief Check URL to se if ts's real
694  * 
695  * Take a URL from the wild, prepend http:// if necessary
696  * and check DNS to see if it's real (or check if is a valid IP address)
697  * 
698  * @param string $url The URL to be validated
699  * @return boolean True if it's a valid URL, fals if something wrong with it
700  */
701 function validate_url(&$url) {
702
703         if(get_config('system','disable_url_validation'))
704                 return true;
705         // no naked subdomains (allow localhost for tests)
706         if(strpos($url,'.') === false && strpos($url,'/localhost/') === false)
707                 return false;
708         if(substr($url,0,4) != 'http')
709                 $url = 'http://' . $url;
710         $h = @parse_url($url);
711
712         if(($h) && (dns_get_record($h['host'], DNS_A + DNS_CNAME + DNS_PTR) || filter_var($h['host'], FILTER_VALIDATE_IP) )) {
713                 return true;
714         }
715         return false;
716 }
717
718 /**
719  * @brief Checks that email is an actual resolvable internet address
720  * 
721  * @param string $addr The email address
722  * @return boolean True if it's a valid email address, false if it's not
723  */
724 function validate_email($addr) {
725
726         if(get_config('system','disable_email_validation'))
727                 return true;
728
729         if(! strpos($addr,'@'))
730                 return false;
731         $h = substr($addr,strpos($addr,'@') + 1);
732
733         if(($h) && (dns_get_record($h, DNS_A + DNS_CNAME + DNS_PTR + DNS_MX) || filter_var($h, FILTER_VALIDATE_IP) )) {
734                 return true;
735         }
736         return false;
737 }
738
739 /**
740  * @brief Check if URL is allowed
741  * 
742  * Check $url against our list of allowed sites,
743  * wildcards allowed. If allowed_sites is unset return true;
744  * 
745  * @param string $url URL which get tested
746  * @return boolean True if url is allowed otherwise return false
747  */
748 function allowed_url($url) {
749
750         $h = @parse_url($url);
751
752         if(! $h) {
753                 return false;
754         }
755
756         $str_allowed = get_config('system','allowed_sites');
757         if(! $str_allowed)
758                 return true;
759
760         $found = false;
761
762         $host = strtolower($h['host']);
763
764         // always allow our own site
765
766         if($host == strtolower($_SERVER['SERVER_NAME']))
767                 return true;
768
769         $fnmatch = function_exists('fnmatch');
770         $allowed = explode(',',$str_allowed);
771
772         if(count($allowed)) {
773                 foreach($allowed as $a) {
774                         $pat = strtolower(trim($a));
775                         if(($fnmatch && fnmatch($pat,$host)) || ($pat == $host)) {
776                                 $found = true;
777                                 break;
778                         }
779                 }
780         }
781         return $found;
782 }
783
784 /**
785  * @brief Check if email address is allowed to register here.
786  * 
787  * Compare against our list (wildcards allowed).
788  * 
789  * @param type $email
790  * @return boolean False if not allowed, true if allowed
791  *    or if allowed list is not configured
792  */
793 function allowed_email($email) {
794
795
796         $domain = strtolower(substr($email,strpos($email,'@') + 1));
797         if(! $domain)
798                 return false;
799
800         $str_allowed = get_config('system','allowed_email');
801         if(! $str_allowed)
802                 return true;
803
804         $found = false;
805
806         $fnmatch = function_exists('fnmatch');
807         $allowed = explode(',',$str_allowed);
808
809         if(count($allowed)) {
810                 foreach($allowed as $a) {
811                         $pat = strtolower(trim($a));
812                         if(($fnmatch && fnmatch($pat,$domain)) || ($pat == $domain)) {
813                                 $found = true;
814                                 break;
815                         }
816                 }
817         }
818         return $found;
819 }
820
821 function avatar_img($email) {
822
823         $a = get_app();
824
825         $avatar['size'] = 175;
826         $avatar['email'] = $email;
827         $avatar['url'] = '';
828         $avatar['success'] = false;
829
830         call_hooks('avatar_lookup', $avatar);
831
832         if(! $avatar['success'])
833                 $avatar['url'] = $a->get_baseurl() . '/images/person-175.jpg';
834
835         logger('Avatar: ' . $avatar['email'] . ' ' . $avatar['url'], LOGGER_DEBUG);
836         return $avatar['url'];
837 }
838
839
840 function parse_xml_string($s,$strict = true) {
841         /// @todo Move this function to the xml class
842         if($strict) {
843                 if(! strstr($s,'<?xml'))
844                         return false;
845                 $s2 = substr($s,strpos($s,'<?xml'));
846         }
847         else
848                 $s2 = $s;
849         libxml_use_internal_errors(true);
850
851         $x = @simplexml_load_string($s2);
852         if(! $x) {
853                 logger('libxml: parse: error: ' . $s2, LOGGER_DATA);
854                 foreach(libxml_get_errors() as $err)
855                         logger('libxml: parse: ' . $err->code." at ".$err->line.":".$err->column." : ".$err->message, LOGGER_DATA);
856                 libxml_clear_errors();
857         }
858         return $x;
859 }
860
861 function scale_external_images($srctext, $include_link = true, $scale_replace = false) {
862
863         // Suppress "view full size"
864         if (intval(get_config('system','no_view_full_size')))
865                 $include_link = false;
866
867         $a = get_app();
868
869         // Picture addresses can contain special characters
870         $s = htmlspecialchars_decode($srctext);
871
872         $matches = null;
873         $c = preg_match_all('/\[img.*?\](.*?)\[\/img\]/ism',$s,$matches,PREG_SET_ORDER);
874         if($c) {
875                 require_once('include/Photo.php');
876                 foreach($matches as $mtch) {
877                         logger('scale_external_image: ' . $mtch[1]);
878
879                         $hostname = str_replace('www.','',substr($a->get_baseurl(),strpos($a->get_baseurl(),'://')+3));
880                         if(stristr($mtch[1],$hostname))
881                                 continue;
882
883                         // $scale_replace, if passed, is an array of two elements. The
884                         // first is the name of the full-size image. The second is the
885                         // name of a remote, scaled-down version of the full size image.
886                         // This allows Friendica to display the smaller remote image if
887                         // one exists, while still linking to the full-size image
888                         if($scale_replace)
889                                 $scaled = str_replace($scale_replace[0], $scale_replace[1], $mtch[1]);
890                         else
891                                 $scaled = $mtch[1];
892                         $i = @fetch_url($scaled);
893                         if(! $i)
894                                 return $srctext;
895
896                         // guess mimetype from headers or filename
897                         $type = guess_image_type($mtch[1],true);
898
899                         if($i) {
900                                 $ph = new Photo($i, $type);
901                                 if($ph->is_valid()) {
902                                         $orig_width = $ph->getWidth();
903                                         $orig_height = $ph->getHeight();
904
905                                         if($orig_width > 640 || $orig_height > 640) {
906
907                                                 $ph->scaleImage(640);
908                                                 $new_width = $ph->getWidth();
909                                                 $new_height = $ph->getHeight();
910                                                 logger('scale_external_images: ' . $orig_width . '->' . $new_width . 'w ' . $orig_height . '->' . $new_height . 'h' . ' match: ' . $mtch[0], LOGGER_DEBUG);
911                                                 $s = str_replace($mtch[0],'[img=' . $new_width . 'x' . $new_height. ']' . $scaled . '[/img]'
912                                                         . "\n" . (($include_link)
913                                                                 ? '[url=' . $mtch[1] . ']' . t('view full size') . '[/url]' . "\n"
914                                                                 : ''),$s);
915                                                 logger('scale_external_images: new string: ' . $s, LOGGER_DEBUG);
916                                         }
917                                 }
918                         }
919                 }
920         }
921
922         // replace the special char encoding
923         $s = htmlspecialchars($s,ENT_NOQUOTES,'UTF-8');
924         return $s;
925 }
926
927
928 function fix_contact_ssl_policy(&$contact,$new_policy) {
929
930         $ssl_changed = false;
931         if((intval($new_policy) == SSL_POLICY_SELFSIGN || $new_policy === 'self') && strstr($contact['url'],'https:')) {
932                 $ssl_changed = true;
933                 $contact['url']     =   str_replace('https:','http:',$contact['url']);
934                 $contact['request'] =   str_replace('https:','http:',$contact['request']);
935                 $contact['notify']  =   str_replace('https:','http:',$contact['notify']);
936                 $contact['poll']    =   str_replace('https:','http:',$contact['poll']);
937                 $contact['confirm'] =   str_replace('https:','http:',$contact['confirm']);
938                 $contact['poco']    =   str_replace('https:','http:',$contact['poco']);
939         }
940
941         if((intval($new_policy) == SSL_POLICY_FULL || $new_policy === 'full') && strstr($contact['url'],'http:')) {
942                 $ssl_changed = true;
943                 $contact['url']     =   str_replace('http:','https:',$contact['url']);
944                 $contact['request'] =   str_replace('http:','https:',$contact['request']);
945                 $contact['notify']  =   str_replace('http:','https:',$contact['notify']);
946                 $contact['poll']    =   str_replace('http:','https:',$contact['poll']);
947                 $contact['confirm'] =   str_replace('http:','https:',$contact['confirm']);
948                 $contact['poco']    =   str_replace('http:','https:',$contact['poco']);
949         }
950
951         if($ssl_changed) {
952                 q("update contact set
953                         url = '%s',
954                         request = '%s',
955                         notify = '%s',
956                         poll = '%s',
957                         confirm = '%s',
958                         poco = '%s'
959                         where id = %d limit 1",
960                         dbesc($contact['url']),
961                         dbesc($contact['request']),
962                         dbesc($contact['notify']),
963                         dbesc($contact['poll']),
964                         dbesc($contact['confirm']),
965                         dbesc($contact['poco']),
966                         intval($contact['id'])
967                 );
968         }
969 }
970
971 function original_url($url, $depth=1, $fetchbody = false) {
972
973         $a = get_app();
974
975         // Remove Analytics Data from Google and other tracking platforms
976         $urldata = parse_url($url);
977         if (is_string($urldata["query"])) {
978                 $query = $urldata["query"];
979                 parse_str($query, $querydata);
980
981                 if (is_array($querydata))
982                         foreach ($querydata AS $param=>$value)
983                                 if (in_array($param, array("utm_source", "utm_medium", "utm_term", "utm_content", "utm_campaign",
984                                                         "wt_mc", "pk_campaign", "pk_kwd", "mc_cid", "mc_eid",
985                                                         "fb_action_ids", "fb_action_types", "fb_ref",
986                                                         "awesm", "wtrid",
987                                                         "woo_campaign", "woo_source", "woo_medium", "woo_content", "woo_term"))) {
988
989                                         $pair = $param."=".urlencode($value);
990                                         $url = str_replace($pair, "", $url);
991
992                                         // Second try: if the url isn't encoded completely
993                                         $pair = $param."=".str_replace(" ", "+", $value);
994                                         $url = str_replace($pair, "", $url);
995
996                                         // Third try: Maybey the url isn't encoded at all
997                                         $pair = $param."=".$value;
998                                         $url = str_replace($pair, "", $url);
999
1000                                         $url = str_replace(array("?&", "&&"), array("?", ""), $url);
1001                                 }
1002
1003                 if (substr($url, -1, 1) == "?")
1004                         $url = substr($url, 0, -1);
1005         }
1006
1007         if ($depth > 10)
1008                 return($url);
1009
1010         $url = trim($url, "'");
1011
1012         $stamp1 = microtime(true);
1013
1014         $siteinfo = array();
1015         $ch = curl_init();
1016         curl_setopt($ch, CURLOPT_URL, $url);
1017         curl_setopt($ch, CURLOPT_HEADER, 1);
1018         curl_setopt($ch, CURLOPT_NOBODY, 1);
1019         curl_setopt($ch, CURLOPT_TIMEOUT, 10);
1020         curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
1021         curl_setopt($ch, CURLOPT_USERAGENT, $a->get_useragent());
1022
1023         $header = curl_exec($ch);
1024         $curl_info = @curl_getinfo($ch);
1025         $http_code = $curl_info['http_code'];
1026         curl_close($ch);
1027
1028         $a->save_timestamp($stamp1, "network");
1029
1030         if ($http_code == 0)
1031                 return($url);
1032
1033         if ((($curl_info['http_code'] == "301") OR ($curl_info['http_code'] == "302"))
1034                 AND (($curl_info['redirect_url'] != "") OR ($curl_info['location'] != ""))) {
1035                 if ($curl_info['redirect_url'] != "")
1036                         return(original_url($curl_info['redirect_url'], ++$depth, $fetchbody));
1037                 else
1038                         return(original_url($curl_info['location'], ++$depth, $fetchbody));
1039         }
1040
1041         // Check for redirects in the meta elements of the body if there are no redirects in the header.
1042         if (!$fetchbody)
1043                 return(original_url($url, ++$depth, true));
1044
1045         // if the file is too large then exit
1046         if ($curl_info["download_content_length"] > 1000000)
1047                 return($url);
1048
1049         // if it isn't a HTML file then exit
1050         if (($curl_info["content_type"] != "") AND !strstr(strtolower($curl_info["content_type"]),"html"))
1051                 return($url);
1052
1053         $stamp1 = microtime(true);
1054
1055         $ch = curl_init();
1056         curl_setopt($ch, CURLOPT_URL, $url);
1057         curl_setopt($ch, CURLOPT_HEADER, 0);
1058         curl_setopt($ch, CURLOPT_NOBODY, 0);
1059         curl_setopt($ch, CURLOPT_TIMEOUT, 10);
1060         curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
1061         curl_setopt($ch, CURLOPT_USERAGENT, $a->get_useragent());
1062
1063         $body = curl_exec($ch);
1064         curl_close($ch);
1065
1066         $a->save_timestamp($stamp1, "network");
1067
1068         if (trim($body) == "")
1069                 return($url);
1070
1071         // Check for redirect in meta elements
1072         $doc = new DOMDocument();
1073         @$doc->loadHTML($body);
1074
1075         $xpath = new DomXPath($doc);
1076
1077         $list = $xpath->query("//meta[@content]");
1078         foreach ($list as $node) {
1079                 $attr = array();
1080                 if ($node->attributes->length)
1081                         foreach ($node->attributes as $attribute)
1082                                 $attr[$attribute->name] = $attribute->value;
1083
1084                 if (@$attr["http-equiv"] == 'refresh') {
1085                         $path = $attr["content"];
1086                         $pathinfo = explode(";", $path);
1087                         $content = "";
1088                         foreach ($pathinfo AS $value)
1089                                 if (substr(strtolower($value), 0, 4) == "url=")
1090                                         return(original_url(substr($value, 4), ++$depth));
1091                 }
1092         }
1093
1094         return($url);
1095 }
1096
1097 function short_link($url) {
1098         require_once('library/slinky.php');
1099         $slinky = new Slinky($url);
1100         $yourls_url = get_config('yourls','url1');
1101         if ($yourls_url) {
1102                 $yourls_username = get_config('yourls','username1');
1103                 $yourls_password = get_config('yourls', 'password1');
1104                 $yourls_ssl = get_config('yourls', 'ssl1');
1105                 $yourls = new Slinky_YourLS();
1106                 $yourls->set('username', $yourls_username);
1107                 $yourls->set('password', $yourls_password);
1108                 $yourls->set('ssl', $yourls_ssl);
1109                 $yourls->set('yourls-url', $yourls_url);
1110                 $slinky->set_cascade( array($yourls, new Slinky_UR1ca(), new Slinky_Trim(), new Slinky_IsGd(), new Slinky_TinyURL()));
1111         } else {
1112                 // setup a cascade of shortening services
1113                 // try to get a short link from these services
1114                 // in the order ur1.ca, trim, id.gd, tinyurl
1115                 $slinky->set_cascade(array(new Slinky_UR1ca(), new Slinky_Trim(), new Slinky_IsGd(), new Slinky_TinyURL()));
1116         }
1117         return $slinky->short();
1118 }
1119
1120 /**
1121  * @brief Encodes content to json
1122  * 
1123  * This function encodes an array to json format
1124  * and adds an application/json HTTP header to the output.
1125  * After finishing the process is getting killed.
1126  * 
1127  * @param array $x The input content
1128  */
1129 function json_return_and_die($x) {
1130         header("content-type: application/json");
1131         echo json_encode($x);
1132         killme();
1133 }