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