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