]> git.mxchange.org Git - friendica.git/blob - include/network.php
rev update
[friendica.git] / include / network.php
1 <?php
2
3
4 // curl wrapper. If binary flag is true, return binary
5 // results. 
6
7 if(! function_exists('fetch_url')) {
8 function fetch_url($url,$binary = false, &$redirects = 0, $timeout = 0, $accept_content=Null) {
9
10         $a = get_app();
11
12         $ch = @curl_init($url);
13         if(($redirects > 8) || (! $ch)) 
14                 return false;
15
16         @curl_setopt($ch, CURLOPT_HEADER, true);
17
18
19 //  These settings aren't needed. We're following the location already. 
20 //      @curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
21 //      @curl_setopt($ch, CURLOPT_MAXREDIRS, 5);
22
23         if (!is_null($accept_content)){
24                 curl_setopt($ch,CURLOPT_HTTPHEADER, array (
25                         "Accept: " . $accept_content
26                 ));
27         }
28
29         @curl_setopt($ch, CURLOPT_RETURNTRANSFER,true);
30         //@curl_setopt($ch, CURLOPT_USERAGENT, "Friendica");
31         @curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/5.0 (compatible; Friendica)");
32
33
34         if(intval($timeout)) {
35                 @curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
36         }
37         else {
38                 $curl_time = intval(get_config('system','curl_timeout'));
39                 @curl_setopt($ch, CURLOPT_TIMEOUT, (($curl_time !== false) ? $curl_time : 60));
40         }
41         // by default we will allow self-signed certs
42         // but you can override this
43
44         $check_cert = get_config('system','verifyssl');
45         @curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, (($check_cert) ? true : false));
46
47         $prx = get_config('system','proxy');
48         if(strlen($prx)) {
49                 @curl_setopt($ch, CURLOPT_HTTPPROXYTUNNEL, 1);
50                 @curl_setopt($ch, CURLOPT_PROXY, $prx);
51                 $prxusr = @get_config('system','proxyuser');
52                 if(strlen($prxusr))
53                         @curl_setopt($ch, CURLOPT_PROXYUSERPWD, $prxusr);
54         }
55         if($binary)
56                 @curl_setopt($ch, CURLOPT_BINARYTRANSFER,1);
57
58         $a->set_curl_code(0);
59
60         // don't let curl abort the entire application
61         // if it throws any errors.
62
63         $s = @curl_exec($ch);
64
65         $base = $s;
66         $curl_info = @curl_getinfo($ch);
67         $http_code = $curl_info['http_code'];
68 //      logger('fetch_url:' . $http_code . ' data: ' . $s);
69         $header = '';
70
71         // Pull out multiple headers, e.g. proxy and continuation headers
72         // allow for HTTP/2.x without fixing code
73
74         while(preg_match('/^HTTP\/[1-2].+? [1-5][0-9][0-9]/',$base)) {
75                 $chunk = substr($base,0,strpos($base,"\r\n\r\n")+4);
76                 $header .= $chunk;
77                 $base = substr($base,strlen($chunk));
78         }
79
80         if($http_code == 301 || $http_code == 302 || $http_code == 303 || $http_code == 307) {
81                 $matches = array();
82                 preg_match('/(Location:|URI:)(.*?)\n/', $header, $matches);
83                 $newurl = trim(array_pop($matches));
84                 if(strpos($newurl,'/') === 0)
85                         $newurl = $url . $newurl;
86                 $url_parsed = @parse_url($newurl);
87                 if (isset($url_parsed)) {
88                         $redirects++;
89                         return fetch_url($newurl,$binary,$redirects,$timeout);
90                 }
91         }
92
93         $a->set_curl_code($http_code);
94
95         $body = substr($s,strlen($header));
96         $a->set_curl_headers($header);
97         @curl_close($ch);
98         return($body);
99 }}
100
101 // post request to $url. $params is an array of post variables.
102
103 if(! function_exists('post_url')) {
104 function post_url($url,$params, $headers = null, &$redirects = 0, $timeout = 0) {
105         $a = get_app();
106         $ch = curl_init($url);
107         if(($redirects > 8) || (! $ch))
108                 return false;
109
110         curl_setopt($ch, CURLOPT_HEADER, true);
111         curl_setopt($ch, CURLOPT_RETURNTRANSFER,true);
112         curl_setopt($ch, CURLOPT_POST,1);
113         curl_setopt($ch, CURLOPT_POSTFIELDS,$params);
114         curl_setopt($ch, CURLOPT_USERAGENT, "Friendica");
115
116         if(intval($timeout)) {
117                 curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
118         }
119         else {
120                 $curl_time = intval(get_config('system','curl_timeout'));
121                 curl_setopt($ch, CURLOPT_TIMEOUT, (($curl_time !== false) ? $curl_time : 60));
122         }
123
124         if(defined('LIGHTTPD')) {
125                 if(!is_array($headers)) {
126                         $headers = array('Expect:');
127                 } else {
128                         if(!in_array('Expect:', $headers)) {
129                                 array_push($headers, 'Expect:');
130                         }
131                 }
132         }
133         if($headers)
134                 curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
135
136         $check_cert = get_config('system','verifyssl');
137         curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, (($check_cert) ? true : false));
138         $prx = get_config('system','proxy');
139         if(strlen($prx)) {
140                 curl_setopt($ch, CURLOPT_HTTPPROXYTUNNEL, 1);
141                 curl_setopt($ch, CURLOPT_PROXY, $prx);
142                 $prxusr = get_config('system','proxyuser');
143                 if(strlen($prxusr))
144                         curl_setopt($ch, CURLOPT_PROXYUSERPWD, $prxusr);
145         }
146
147         $a->set_curl_code(0);
148
149         // don't let curl abort the entire application
150         // if it throws any errors.
151
152         $s = @curl_exec($ch);
153
154         $base = $s;
155         $curl_info = curl_getinfo($ch);
156         $http_code = $curl_info['http_code'];
157
158         $header = '';
159
160         // Pull out multiple headers, e.g. proxy and continuation headers
161         // allow for HTTP/2.x without fixing code
162
163         while(preg_match('/^HTTP\/[1-2].+? [1-5][0-9][0-9]/',$base)) {
164                 $chunk = substr($base,0,strpos($base,"\r\n\r\n")+4);
165                 $header .= $chunk;
166                 $base = substr($base,strlen($chunk));
167         }
168
169         if($http_code == 301 || $http_code == 302 || $http_code == 303) {
170         $matches = array();
171         preg_match('/(Location:|URI:)(.*?)\n/', $header, $matches);
172         $newurl = trim(array_pop($matches));
173                 if(strpos($newurl,'/') === 0)
174                         $newurl = $url . $newurl;
175         $url_parsed = @parse_url($newurl);
176         if (isset($url_parsed)) {
177             $redirects++;
178             return fetch_url($newurl,false,$redirects,$timeout);
179         }
180     }
181         $a->set_curl_code($http_code);
182         $body = substr($s,strlen($header));
183
184         $a->set_curl_headers($header);
185
186         curl_close($ch);
187         return($body);
188 }}
189
190 // Generic XML return
191 // Outputs a basic dfrn XML status structure to STDOUT, with a <status> variable 
192 // of $st and an optional text <message> of $message and terminates the current process. 
193
194 if(! function_exists('xml_status')) {
195 function xml_status($st, $message = '') {
196
197         $xml_message = ((strlen($message)) ? "\t<message>" . xmlify($message) . "</message>\r\n" : '');
198
199         if($st)
200                 logger('xml_status returning non_zero: ' . $st . " message=" . $message);
201
202         header( "Content-type: text/xml" );
203         echo '<?xml version="1.0" encoding="UTF-8"?>'."\r\n";
204         echo "<result>\r\n\t<status>$st</status>\r\n$xml_message</result>\r\n";
205         killme();
206 }}
207
208
209 if(! function_exists('http_status_exit')) {
210 function http_status_exit($val) {
211
212     $err = '';
213         if($val >= 400)
214                 $err = 'Error';
215         if($val >= 200 && $val < 300)
216                 $err = 'OK';
217
218         logger('http_status_exit ' . $val);     
219         header($_SERVER["SERVER_PROTOCOL"] . ' ' . $val . ' ' . $err);
220         killme();
221
222 }}
223
224
225 // convert an XML document to a normalised, case-corrected array
226 // used by webfinger
227
228 if(! function_exists('convert_xml_element_to_array')) {
229 function convert_xml_element_to_array($xml_element, &$recursion_depth=0) {
230
231         // If we're getting too deep, bail out
232         if ($recursion_depth > 512) {
233                 return(null);
234         }
235
236         if (!is_string($xml_element) &&
237         !is_array($xml_element) &&
238         (get_class($xml_element) == 'SimpleXMLElement')) {
239                 $xml_element_copy = $xml_element;
240                 $xml_element = get_object_vars($xml_element);
241         }
242
243         if (is_array($xml_element)) {
244                 $result_array = array();
245                 if (count($xml_element) <= 0) {
246                         return (trim(strval($xml_element_copy)));
247                 }
248
249                 foreach($xml_element as $key=>$value) {
250
251                         $recursion_depth++;
252                         $result_array[strtolower($key)] =
253                 convert_xml_element_to_array($value, $recursion_depth);
254                         $recursion_depth--;
255                 }
256                 if ($recursion_depth == 0) {
257                         $temp_array = $result_array;
258                         $result_array = array(
259                                 strtolower($xml_element_copy->getName()) => $temp_array,
260                         );
261                 }
262
263                 return ($result_array);
264
265         } else {
266                 return (trim(strval($xml_element)));
267         }
268 }}
269
270 // Given an email style address, perform webfinger lookup and 
271 // return the resulting DFRN profile URL, or if no DFRN profile URL
272 // is located, returns an OStatus subscription template (prefixed 
273 // with the string 'stat:' to identify it as on OStatus template).
274 // If this isn't an email style address just return $s.
275 // Return an empty string if email-style addresses but webfinger fails,
276 // or if the resultant personal XRD doesn't contain a supported 
277 // subscription/friend-request attribute.
278
279 // amended 7/9/2011 to return an hcard which could save potentially loading 
280 // a lengthy content page to scrape dfrn attributes
281
282 if(! function_exists('webfinger_dfrn')) {
283 function webfinger_dfrn($s,&$hcard) {
284         if(! strstr($s,'@')) {
285                 return $s;
286         }
287         $profile_link = '';
288
289         $links = webfinger($s);
290         logger('webfinger_dfrn: ' . $s . ':' . print_r($links,true), LOGGER_DATA);
291         if(count($links)) {
292                 foreach($links as $link) {
293                         if($link['@attributes']['rel'] === NAMESPACE_DFRN)
294                                 $profile_link = $link['@attributes']['href'];
295                         if($link['@attributes']['rel'] === NAMESPACE_OSTATUSSUB)
296                                 $profile_link = 'stat:' . $link['@attributes']['template'];     
297                         if($link['@attributes']['rel'] === 'http://microformats.org/profile/hcard')
298                                 $hcard = $link['@attributes']['href'];                          
299                 }
300         }
301         return $profile_link;
302 }}
303
304 // Given an email style address, perform webfinger lookup and 
305 // return the array of link attributes from the personal XRD file.
306 // On error/failure return an empty array.
307
308
309 if(! function_exists('webfinger')) {
310 function webfinger($s, $debug = false) {
311         $host = '';
312         if(strstr($s,'@')) {
313                 $host = substr($s,strpos($s,'@') + 1);
314         }
315         if(strlen($host)) {
316                 $tpl = fetch_lrdd_template($host);
317                 logger('webfinger: lrdd template: ' . $tpl);
318                 if(strlen($tpl)) {
319                         $pxrd = str_replace('{uri}', urlencode('acct:' . $s), $tpl);
320                         logger('webfinger: pxrd: ' . $pxrd);
321                         $links = fetch_xrd_links($pxrd);
322                         if(! count($links)) {
323                                 // try with double slashes
324                                 $pxrd = str_replace('{uri}', urlencode('acct://' . $s), $tpl);
325                                 logger('webfinger: pxrd: ' . $pxrd);
326                                 $links = fetch_xrd_links($pxrd);
327                         }
328                         return $links;
329                 }
330         }
331         return array();
332 }}
333
334 if(! function_exists('lrdd')) {
335 function lrdd($uri, $debug = false) {
336
337         $a = get_app();
338
339         // default priority is host priority, host-meta first
340
341         $priority = 'host';
342
343         // All we have is an email address. Resource-priority is irrelevant
344         // because our URI isn't directly resolvable.
345
346         if(strstr($uri,'@')) {  
347                 return(webfinger($uri));
348         }
349
350         // get the host meta file
351
352         $host = @parse_url($uri);
353
354         if($host) {
355                 $url  = ((x($host,'scheme')) ? $host['scheme'] : 'http') . '://';
356                 $url .= $host['host'] . '/.well-known/host-meta' ;
357         }
358         else
359                 return array();
360
361         logger('lrdd: constructed url: ' . $url);
362
363         $xml = fetch_url($url);
364         $headers = $a->get_curl_headers();
365
366         if (! $xml)
367                 return array();
368
369         logger('lrdd: host_meta: ' . $xml, LOGGER_DATA);
370
371         if(! stristr($xml,'<xrd'))
372                 return array();
373
374         $h = parse_xml_string($xml);
375         if(! $h)
376                 return array();
377
378         $arr = convert_xml_element_to_array($h);
379
380         if(isset($arr['xrd']['property'])) {
381                 $property = $arr['crd']['property'];
382                 if(! isset($property[0]))
383                         $properties = array($property);
384                 else
385                         $properties = $property;
386                 foreach($properties as $prop)
387                         if((string) $prop['@attributes'] === 'http://lrdd.net/priority/resource')
388                                 $priority = 'resource';
389         } 
390
391         // save the links in case we need them
392
393         $links = array();
394
395         if(isset($arr['xrd']['link'])) {
396                 $link = $arr['xrd']['link'];
397                 if(! isset($link[0]))
398                         $links = array($link);
399                 else
400                         $links = $link;
401         }
402
403         // do we have a template or href?
404
405         if(count($links)) {
406                 foreach($links as $link) {
407                         if($link['@attributes']['rel'] && attribute_contains($link['@attributes']['rel'],'lrdd')) {
408                                 if(x($link['@attributes'],'template'))
409                                         $tpl = $link['@attributes']['template'];
410                                 elseif(x($link['@attributes'],'href'))
411                                         $href = $link['@attributes']['href'];
412                         }
413                 }               
414         }
415
416         if((! isset($tpl)) || (! strpos($tpl,'{uri}')))
417                 $tpl = '';
418
419         if($priority === 'host') {
420                 if(strlen($tpl)) 
421                         $pxrd = str_replace('{uri}', urlencode($uri), $tpl);
422                 elseif(isset($href))
423                         $pxrd = $href;
424                 if(isset($pxrd)) {
425                         logger('lrdd: (host priority) pxrd: ' . $pxrd);
426                         $links = fetch_xrd_links($pxrd);
427                         return $links;
428                 }
429
430                 $lines = explode("\n",$headers);
431                 if(count($lines)) {
432                         foreach($lines as $line) {                              
433                                 if((stristr($line,'link:')) && preg_match('/<([^>].*)>.*rel\=[\'\"]lrdd[\'\"]/',$line,$matches)) {
434                                         return(fetch_xrd_links($matches[1]));
435                                         break;
436                                 }
437                         }
438                 }
439         }
440
441
442         // priority 'resource'
443
444
445         $html = fetch_url($uri);
446         $headers = $a->get_curl_headers();
447         logger('lrdd: headers=' . $headers, LOGGER_DEBUG);
448
449         // don't try and parse raw xml as html
450         if(! strstr($html,'<?xml')) {
451                 require_once('library/HTML5/Parser.php');
452
453                 try {
454                         $dom = HTML5_Parser::parse($html);
455                 } catch (DOMException $e) {
456                         logger('lrdd: parse error: ' . $e);
457                 }
458
459                 if(isset($dom) && $dom) {
460                         $items = $dom->getElementsByTagName('link');
461                         foreach($items as $item) {
462                                 $x = $item->getAttribute('rel');
463                                 if($x == "lrdd") {
464                                         $pagelink = $item->getAttribute('href');
465                                         break;
466                                 }
467                         }
468                 }
469         }
470
471         if(isset($pagelink))
472                 return(fetch_xrd_links($pagelink));
473
474         // next look in HTTP headers
475
476         $lines = explode("\n",$headers);
477         if(count($lines)) {
478                 foreach($lines as $line) {                              
479                         // TODO alter the following regex to support multiple relations (space separated)
480                         if((stristr($line,'link:')) && preg_match('/<([^>].*)>.*rel\=[\'\"]lrdd[\'\"]/',$line,$matches)) {
481                                 $pagelink = $matches[1];
482                                 break;
483                         }
484                         // don't try and run feeds through the html5 parser
485                         if(stristr($line,'content-type:') && ((stristr($line,'application/atom+xml')) || (stristr($line,'application/rss+xml'))))
486                                 return array();
487                         if(stristr($html,'<rss') || stristr($html,'<feed'))
488                                 return array();
489                 }
490         }
491
492         if(isset($pagelink))
493                 return(fetch_xrd_links($pagelink));
494
495         // If we haven't found any links, return the host xrd links (which we have already fetched)
496
497         if(isset($links))
498                 return $links;
499
500         return array();
501
502 }}
503
504
505
506 // Given a host name, locate the LRDD template from that
507 // host. Returns the LRDD template or an empty string on
508 // error/failure.
509
510 if(! function_exists('fetch_lrdd_template')) {
511 function fetch_lrdd_template($host) {
512         $tpl = '';
513
514         $url1 = 'https://' . $host . '/.well-known/host-meta' ;
515         $url2 = 'http://' . $host . '/.well-known/host-meta' ;
516         $links = fetch_xrd_links($url1);
517         logger('fetch_lrdd_template from: ' . $url1);
518         logger('template (https): ' . print_r($links,true));
519         if(! count($links)) {
520                 logger('fetch_lrdd_template from: ' . $url2);
521                 $links = fetch_xrd_links($url2);
522                 logger('template (http): ' . print_r($links,true));
523         }
524         if(count($links)) {
525                 foreach($links as $link)
526                         if($link['@attributes']['rel'] && $link['@attributes']['rel'] === 'lrdd')
527                                 $tpl = $link['@attributes']['template'];
528         }
529         if(! strpos($tpl,'{uri}'))
530                 $tpl = '';
531         return $tpl;
532 }}
533
534 // Given a URL, retrieve the page as an XRD document.
535 // Return an array of links.
536 // on error/failure return empty array.
537
538 if(! function_exists('fetch_xrd_links')) {
539 function fetch_xrd_links($url) {
540
541         $xrd_timeout = intval(get_config('system','xrd_timeout'));
542         $redirects = 0;
543         $xml = fetch_url($url,false,$redirects,(($xrd_timeout) ? $xrd_timeout : 20));
544
545         logger('fetch_xrd_links: ' . $xml, LOGGER_DATA);
546
547         if ((! $xml) || (! stristr($xml,'<xrd')))
548                 return array();
549
550         // fix diaspora's bad xml
551         $xml = str_replace(array('href=&quot;','&quot;/>'),array('href="','"/>'),$xml);
552
553         $h = parse_xml_string($xml);
554         if(! $h)
555                 return array();
556
557         $arr = convert_xml_element_to_array($h);
558
559         $links = array();
560
561         if(isset($arr['xrd']['link'])) {
562                 $link = $arr['xrd']['link'];
563                 if(! isset($link[0]))
564                         $links = array($link);
565                 else
566                         $links = $link;
567         }
568         if(isset($arr['xrd']['alias'])) {
569                 $alias = $arr['xrd']['alias'];
570                 if(! isset($alias[0]))
571                         $aliases = array($alias);
572                 else
573                         $aliases = $alias;
574                 if(is_array($aliases) && count($aliases)) {
575                         foreach($aliases as $alias) {
576                                 $links[]['@attributes'] = array('rel' => 'alias' , 'href' => $alias);
577                         }
578                 }
579         }
580
581         logger('fetch_xrd_links: ' . print_r($links,true), LOGGER_DATA);
582
583         return $links;
584
585 }}
586
587
588 // Take a URL from the wild, prepend http:// if necessary
589 // and check DNS to see if it's real (or check if is a valid IP address)
590 // return true if it's OK, false if something is wrong with it
591
592 if(! function_exists('validate_url')) {
593 function validate_url(&$url) {
594         
595         // no naked subdomains (allow localhost for tests)
596         if(strpos($url,'.') === false && strpos($url,'/localhost/') === false)
597                 return false;
598         if(substr($url,0,4) != 'http')
599                 $url = 'http://' . $url;
600         $h = @parse_url($url);
601         
602         if(($h) && (dns_get_record($h['host'], DNS_A + DNS_CNAME + DNS_PTR) || filter_var($h['host'], FILTER_VALIDATE_IP) )) {
603                 return true;
604         }
605         return false;
606 }}
607
608 // checks that email is an actual resolvable internet address
609
610 if(! function_exists('validate_email')) {
611 function validate_email($addr) {
612
613         if(get_config('system','disable_email_validation'))
614                 return true;
615
616         if(! strpos($addr,'@'))
617                 return false;
618         $h = substr($addr,strpos($addr,'@') + 1);
619
620         if(($h) && (dns_get_record($h, DNS_A + DNS_CNAME + DNS_PTR + DNS_MX) || filter_var($h['host'], FILTER_VALIDATE_IP) )) {
621                 return true;
622         }
623         return false;
624 }}
625
626 // Check $url against our list of allowed sites,
627 // wildcards allowed. If allowed_sites is unset return true;
628 // If url is allowed, return true.
629 // otherwise, return false
630
631 if(! function_exists('allowed_url')) {
632 function allowed_url($url) {
633
634         $h = @parse_url($url);
635
636         if(! $h) {
637                 return false;
638         }
639
640         $str_allowed = get_config('system','allowed_sites');
641         if(! $str_allowed)
642                 return true;
643
644         $found = false;
645
646         $host = strtolower($h['host']);
647
648         // always allow our own site
649
650         if($host == strtolower($_SERVER['SERVER_NAME']))
651                 return true;
652
653         $fnmatch = function_exists('fnmatch');
654         $allowed = explode(',',$str_allowed);
655
656         if(count($allowed)) {
657                 foreach($allowed as $a) {
658                         $pat = strtolower(trim($a));
659                         if(($fnmatch && fnmatch($pat,$host)) || ($pat == $host)) {
660                                 $found = true; 
661                                 break;
662                         }
663                 }
664         }
665         return $found;
666 }}
667
668 // check if email address is allowed to register here.
669 // Compare against our list (wildcards allowed).
670 // Returns false if not allowed, true if allowed or if
671 // allowed list is not configured.
672
673 if(! function_exists('allowed_email')) {
674 function allowed_email($email) {
675
676
677         $domain = strtolower(substr($email,strpos($email,'@') + 1));
678         if(! $domain)
679                 return false;
680
681         $str_allowed = get_config('system','allowed_email');
682         if(! $str_allowed)
683                 return true;
684
685         $found = false;
686
687         $fnmatch = function_exists('fnmatch');
688         $allowed = explode(',',$str_allowed);
689
690         if(count($allowed)) {
691                 foreach($allowed as $a) {
692                         $pat = strtolower(trim($a));
693                         if(($fnmatch && fnmatch($pat,$domain)) || ($pat == $domain)) {
694                                 $found = true; 
695                                 break;
696                         }
697                 }
698         }
699         return $found;
700 }}
701
702
703 if(! function_exists('avatar_img')) {
704 function avatar_img($email) {
705
706         $a = get_app();
707
708         $avatar['size'] = 175;
709         $avatar['email'] = $email;
710         $avatar['url'] = '';
711         $avatar['success'] = false;
712
713         call_hooks('avatar_lookup', $avatar);
714
715         if(! $avatar['success'])
716                 $avatar['url'] = $a->get_baseurl() . '/images/person-175.jpg';
717
718         logger('Avatar: ' . $avatar['email'] . ' ' . $avatar['url'], LOGGER_DEBUG);
719         return $avatar['url'];
720 }}
721
722
723 if(! function_exists('parse_xml_string')) {
724 function parse_xml_string($s,$strict = true) {
725         if($strict) {
726                 if(! strstr($s,'<?xml'))
727                         return false;
728                 $s2 = substr($s,strpos($s,'<?xml'));
729         }
730         else
731                 $s2 = $s;
732         libxml_use_internal_errors(true);
733
734         $x = @simplexml_load_string($s2);
735         if(! $x) {
736                 logger('libxml: parse: error: ' . $s2, LOGGER_DATA);
737                 foreach(libxml_get_errors() as $err)
738                         logger('libxml: parse: ' . $err->code." at ".$err->line.":".$err->column." : ".$err->message, LOGGER_DATA);
739                 libxml_clear_errors();
740         }
741         return $x;
742 }}
743
744 function add_fcontact($arr,$update = false) {
745
746         if($update) {
747                 $r = q("UPDATE `fcontact` SET
748                         `name` = '%s',
749                         `photo` = '%s',
750                         `request` = '%s',
751                         `nick` = '%s',
752                         `addr` = '%s',
753                         `batch` = '%s',
754                         `notify` = '%s',
755                         `poll` = '%s',
756                         `confirm` = '%s',
757                         `alias` = '%s',
758                         `pubkey` = '%s',
759                         `updated` = '%s'
760                         WHERE `url` = '%s' AND `network` = '%s' LIMIT 1", 
761                         dbesc($arr['name']),
762                         dbesc($arr['photo']),
763                         dbesc($arr['request']),
764                         dbesc($arr['nick']),
765                         dbesc($arr['addr']),
766                         dbesc($arr['batch']),
767                         dbesc($arr['notify']),
768                         dbesc($arr['poll']),
769                         dbesc($arr['confirm']),
770                         dbesc($arr['alias']),
771                         dbesc($arr['pubkey']),
772                         dbesc(datetime_convert()),
773                         dbesc($arr['url']),
774                         dbesc($arr['network'])
775                 );
776         }
777         else {
778                 $r = q("insert into fcontact ( `url`,`name`,`photo`,`request`,`nick`,`addr`,
779                         `batch`, `notify`,`poll`,`confirm`,`network`,`alias`,`pubkey`,`updated` )
780                         values('%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s')",
781                         dbesc($arr['url']),
782                         dbesc($arr['name']),
783                         dbesc($arr['photo']),
784                         dbesc($arr['request']),
785                         dbesc($arr['nick']),
786                         dbesc($arr['addr']),
787                         dbesc($arr['batch']),
788                         dbesc($arr['notify']),
789                         dbesc($arr['poll']),
790                         dbesc($arr['confirm']),
791                         dbesc($arr['network']),
792                         dbesc($arr['alias']),
793                         dbesc($arr['pubkey']),
794                         dbesc(datetime_convert())
795                 );
796         }
797
798         return $r;
799 }
800
801
802 function scale_external_images($s, $include_link = true, $scale_replace = false) {
803
804         $a = get_app();
805
806         // Picture addresses can contain special characters
807         $s = htmlspecialchars_decode($s);
808
809         $matches = null;
810         $c = preg_match_all('/\[img.*?\](.*?)\[\/img\]/ism',$s,$matches,PREG_SET_ORDER);
811         if($c) {
812                 require_once('include/Photo.php');
813                 foreach($matches as $mtch) {
814                         logger('scale_external_image: ' . $mtch[1]);
815
816                         $hostname = str_replace('www.','',substr($a->get_baseurl(),strpos($a->get_baseurl(),'://')+3));
817                         if(stristr($mtch[1],$hostname))
818                                 continue;
819
820                         // $scale_replace, if passed, is an array of two elements. The
821                         // first is the name of the full-size image. The second is the
822                         // name of a remote, scaled-down version of the full size image.
823                         // This allows Friendica to display the smaller remote image if
824                         // one exists, while still linking to the full-size image
825                         if($scale_replace)
826                                 $scaled = str_replace($scale_replace[0], $scale_replace[1], $mtch[1]);
827                         else
828                                 $scaled = $mtch[1];
829                         $i = fetch_url($scaled);
830
831                         $cachefile = get_cachefile(hash("md5", $scaled));
832                         if ($cachefile != '')
833                                 file_put_contents($cachefile, $i);
834
835                         // guess mimetype from headers or filename
836                         $type = guess_image_type($mtch[1],true);
837
838                         if($i) {
839                                 $ph = new Photo($i, $type);
840                                 if($ph->is_valid()) {
841                                         $orig_width = $ph->getWidth();
842                                         $orig_height = $ph->getHeight();
843
844                                         if($orig_width > 640 || $orig_height > 640) {
845
846                                                 $ph->scaleImage(640);
847                                                 $new_width = $ph->getWidth();
848                                                 $new_height = $ph->getHeight();
849                                                 logger('scale_external_images: ' . $orig_width . '->' . $new_width . 'w ' . $orig_height . '->' . $new_height . 'h' . ' match: ' . $mtch[0], LOGGER_DEBUG);
850                                                 $s = str_replace($mtch[0],'[img=' . $new_width . 'x' . $new_height. ']' . $scaled . '[/img]'
851                                                         . "\n" . (($include_link) 
852                                                                 ? '[url=' . $mtch[1] . ']' . t('view full size') . '[/url]' . "\n"
853                                                                 : ''),$s);
854                                                 logger('scale_external_images: new string: ' . $s, LOGGER_DEBUG);
855                                         }
856                                 }
857                         }
858                 }
859         }
860
861         // replace the special char encoding
862         $s = htmlspecialchars($s,ENT_NOQUOTES,'UTF-8');
863         return $s;
864 }
865
866
867 function fix_contact_ssl_policy(&$contact,$new_policy) {
868
869         $ssl_changed = false;
870         if((intval($new_policy) == SSL_POLICY_SELFSIGN || $new_policy === 'self') && strstr($contact['url'],'https:')) {
871                 $ssl_changed = true;
872                 $contact['url']     =   str_replace('https:','http:',$contact['url']);
873                 $contact['request'] =   str_replace('https:','http:',$contact['request']);
874                 $contact['notify']  =   str_replace('https:','http:',$contact['notify']);
875                 $contact['poll']    =   str_replace('https:','http:',$contact['poll']);
876                 $contact['confirm'] =   str_replace('https:','http:',$contact['confirm']);
877                 $contact['poco']    =   str_replace('https:','http:',$contact['poco']);
878         }
879
880         if((intval($new_policy) == SSL_POLICY_FULL || $new_policy === 'full') && strstr($contact['url'],'http:')) {
881                 $ssl_changed = true;
882                 $contact['url']     =   str_replace('http:','https:',$contact['url']);
883                 $contact['request'] =   str_replace('http:','https:',$contact['request']);
884                 $contact['notify']  =   str_replace('http:','https:',$contact['notify']);
885                 $contact['poll']    =   str_replace('http:','https:',$contact['poll']);
886                 $contact['confirm'] =   str_replace('http:','https:',$contact['confirm']);
887                 $contact['poco']    =   str_replace('http:','https:',$contact['poco']);
888         }
889
890         if($ssl_changed) {
891                 q("update contact set 
892                         url = '%s', 
893                         request = '%s',
894                         notify = '%s',
895                         poll = '%s',
896                         confirm = '%s',
897                         poco = '%s'
898                         where id = %d limit 1",
899                         dbesc($contact['url']),
900                         dbesc($contact['request']),
901                         dbesc($contact['notify']),
902                         dbesc($contact['poll']),
903                         dbesc($contact['confirm']),
904                         dbesc($contact['poco']),
905                         intval($contact['id'])
906                 );
907         }
908 }
909
910
911
912 /**
913  * xml2array() will convert the given XML text to an array in the XML structure.
914  * Link: http://www.bin-co.com/php/scripts/xml2array/
915  * Portions significantly re-written by mike@macgirvin.com for Friendica (namespaces, lowercase tags, get_attribute default changed, more...)
916  * Arguments : $contents - The XML text
917  *                $namespaces - true or false include namespace information in the returned array as array elements.
918  *                $get_attributes - 1 or 0. If this is 1 the function will get the attributes as well as the tag values - this results in a different array structure in the return value.
919  *                $priority - Can be 'tag' or 'attribute'. This will change the way the resulting array sturcture. For 'tag', the tags are given more importance.
920  * Return: The parsed XML in an array form. Use print_r() to see the resulting array structure.
921  * Examples: $array =  xml2array(file_get_contents('feed.xml'));
922  *              $array =  xml2array(file_get_contents('feed.xml', true, 1, 'attribute'));
923  */ 
924
925 function xml2array($contents, $namespaces = true, $get_attributes=1, $priority = 'attribute') {
926     if(!$contents) return array();
927
928     if(!function_exists('xml_parser_create')) {
929         logger('xml2array: parser function missing');
930         return array();
931     }
932
933
934         libxml_use_internal_errors(true);
935         libxml_clear_errors();
936
937         if($namespaces)
938             $parser = @xml_parser_create_ns("UTF-8",':');
939         else
940             $parser = @xml_parser_create();
941
942         if(! $parser) {
943                 logger('xml2array: xml_parser_create: no resource');
944                 return array();
945         }
946
947     xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, "UTF-8"); 
948         // http://minutillo.com/steve/weblog/2004/6/17/php-xml-and-character-encodings-a-tale-of-sadness-rage-and-data-loss
949     xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
950     xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, 1);
951     @xml_parse_into_struct($parser, trim($contents), $xml_values);
952     @xml_parser_free($parser);
953
954     if(! $xml_values) {
955                 logger('xml2array: libxml: parse error: ' . $contents, LOGGER_DATA);
956                 foreach(libxml_get_errors() as $err)
957                         logger('libxml: parse: ' . $err->code . " at " . $err->line . ":" . $err->column . " : " . $err->message, LOGGER_DATA);
958                 libxml_clear_errors();
959                 return;
960         }
961
962     //Initializations
963     $xml_array = array();
964     $parents = array();
965     $opened_tags = array();
966     $arr = array();
967
968     $current = &$xml_array; // Reference
969
970     // Go through the tags.
971     $repeated_tag_index = array(); // Multiple tags with same name will be turned into an array
972     foreach($xml_values as $data) {
973         unset($attributes,$value); // Remove existing values, or there will be trouble
974
975         // This command will extract these variables into the foreach scope
976         // tag(string), type(string), level(int), attributes(array).
977         extract($data); // We could use the array by itself, but this cooler.
978
979         $result = array();
980         $attributes_data = array();
981         
982         if(isset($value)) {
983             if($priority == 'tag') $result = $value;
984             else $result['value'] = $value; // Put the value in a assoc array if we are in the 'Attribute' mode
985         }
986
987         //Set the attributes too.
988         if(isset($attributes) and $get_attributes) {
989             foreach($attributes as $attr => $val) {
990                 if($priority == 'tag') $attributes_data[$attr] = $val;
991                 else $result['@attributes'][$attr] = $val; // Set all the attributes in a array called 'attr'
992             }
993         }
994
995         // See tag status and do the needed.
996                 if($namespaces && strpos($tag,':')) {
997                         $namespc = substr($tag,0,strrpos($tag,':')); 
998                         $tag = strtolower(substr($tag,strlen($namespc)+1));
999                         $result['@namespace'] = $namespc;
1000                 }
1001                 $tag = strtolower($tag);
1002
1003                 if($type == "open") {   // The starting of the tag '<tag>'
1004             $parent[$level-1] = &$current;
1005             if(!is_array($current) or (!in_array($tag, array_keys($current)))) { // Insert New tag
1006                 $current[$tag] = $result;
1007                 if($attributes_data) $current[$tag. '_attr'] = $attributes_data;
1008                 $repeated_tag_index[$tag.'_'.$level] = 1;
1009
1010                 $current = &$current[$tag];
1011
1012             } else { // There was another element with the same tag name
1013
1014                 if(isset($current[$tag][0])) { // If there is a 0th element it is already an array
1015                     $current[$tag][$repeated_tag_index[$tag.'_'.$level]] = $result;
1016                     $repeated_tag_index[$tag.'_'.$level]++;
1017                 } else { // This section will make the value an array if multiple tags with the same name appear together
1018                     $current[$tag] = array($current[$tag],$result); // This will combine the existing item and the new item together to make an array
1019                     $repeated_tag_index[$tag.'_'.$level] = 2;
1020                     
1021                     if(isset($current[$tag.'_attr'])) { // The attribute of the last(0th) tag must be moved as well
1022                         $current[$tag]['0_attr'] = $current[$tag.'_attr'];
1023                         unset($current[$tag.'_attr']);
1024                     }
1025
1026                 }
1027                 $last_item_index = $repeated_tag_index[$tag.'_'.$level]-1;
1028                 $current = &$current[$tag][$last_item_index];
1029             }
1030
1031         } elseif($type == "complete") { // Tags that ends in 1 line '<tag />'
1032             //See if the key is already taken.
1033             if(!isset($current[$tag])) { //New Key
1034                 $current[$tag] = $result;
1035                 $repeated_tag_index[$tag.'_'.$level] = 1;
1036                 if($priority == 'tag' and $attributes_data) $current[$tag. '_attr'] = $attributes_data;
1037
1038             } else { // If taken, put all things inside a list(array)
1039                 if(isset($current[$tag][0]) and is_array($current[$tag])) { // If it is already an array...
1040
1041                     // ...push the new element into that array.
1042                     $current[$tag][$repeated_tag_index[$tag.'_'.$level]] = $result;
1043                     
1044                     if($priority == 'tag' and $get_attributes and $attributes_data) {
1045                         $current[$tag][$repeated_tag_index[$tag.'_'.$level] . '_attr'] = $attributes_data;
1046                     }
1047                     $repeated_tag_index[$tag.'_'.$level]++;
1048
1049                 } else { // If it is not an array...
1050                     $current[$tag] = array($current[$tag],$result); //...Make it an array using using the existing value and the new value
1051                     $repeated_tag_index[$tag.'_'.$level] = 1;
1052                     if($priority == 'tag' and $get_attributes) {
1053                         if(isset($current[$tag.'_attr'])) { // The attribute of the last(0th) tag must be moved as well
1054                             
1055                             $current[$tag]['0_attr'] = $current[$tag.'_attr'];
1056                             unset($current[$tag.'_attr']);
1057                         }
1058                         
1059                         if($attributes_data) {
1060                             $current[$tag][$repeated_tag_index[$tag.'_'.$level] . '_attr'] = $attributes_data;
1061                         }
1062                     }
1063                     $repeated_tag_index[$tag.'_'.$level]++; // 0 and 1 indexes are already taken
1064                 }
1065             }
1066
1067         } elseif($type == 'close') { // End of tag '</tag>'
1068             $current = &$parent[$level-1];
1069         }
1070     }
1071     
1072     return($xml_array);
1073 }