4 * @file include/network.php
7 require_once("include/xml.php");
13 * If binary flag is true, return binary results.
14 * Set the cookiejar argument to a string (e.g. "/tmp/friendica-cookies.txt")
15 * to preserve cookies from one request to the next.
17 * @param string $url URL to fetch
18 * @param boolean $binary default false
19 * TRUE if asked to return binary results (file download)
20 * @param integer $redirects The recursion counter for internal use - default 0
21 * @param integer $timeout Timeout in seconds, default system config value or 60 seconds
22 * @param string $accept_content supply Accept: header with 'accept_content' as the value
23 * @param string $cookiejar Path to cookie jar file
25 * @return string The fetched content
27 function fetch_url($url,$binary = false, &$redirects = 0, $timeout = 0, $accept_content=Null, $cookiejar = 0) {
33 array('timeout'=>$timeout,
34 'accept_content'=>$accept_content,
35 'cookiejar'=>$cookiejar
42 * @brief fetches an URL.
44 * @param string $url URL to fetch
45 * @param boolean $binary default false
46 * TRUE if asked to return binary results (file download)
47 * @param int $redirects The recursion counter for internal use - default 0
48 * @param array $opts (optional parameters) assoziative array with:
49 * 'accept_content' => supply Accept: header with 'accept_content' as the value
50 * 'timeout' => int Timeout in seconds, default system config value or 60 seconds
51 * 'http_auth' => username:password
52 * 'novalidate' => do not validate SSL certs, default is to validate using our CA list
53 * 'nobody' => only return the header
54 * 'cookiejar' => path to cookie jar file
56 * @return array an assoziative array with:
57 * int 'return_code' => HTTP return code or 0 if timeout or failure
58 * boolean 'success' => boolean true (if HTTP 2xx result) or false
59 * string 'redirect_url' => in case of redirect, content was finally retrieved from this URL
60 * string 'header' => HTTP headers
61 * string 'body' => fetched content
63 function z_fetch_url($url,$binary = false, &$redirects = 0, $opts=array()) {
65 $ret = array('return_code' => 0, 'success' => false, 'header' => "", 'body' => "");
68 $stamp1 = microtime(true);
72 $ch = @curl_init($url);
73 if(($redirects > 8) || (! $ch))
76 @curl_setopt($ch, CURLOPT_HEADER, true);
78 if(x($opts,"cookiejar")) {
79 curl_setopt($ch, CURLOPT_COOKIEJAR, $opts["cookiejar"]);
80 curl_setopt($ch, CURLOPT_COOKIEFILE, $opts["cookiejar"]);
83 // These settings aren't needed. We're following the location already.
84 // @curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
85 // @curl_setopt($ch, CURLOPT_MAXREDIRS, 5);
87 if (x($opts,'accept_content')){
88 curl_setopt($ch,CURLOPT_HTTPHEADER, array (
89 "Accept: " . $opts['accept_content']
93 @curl_setopt($ch, CURLOPT_RETURNTRANSFER,true);
94 @curl_setopt($ch, CURLOPT_USERAGENT, $a->get_useragent());
98 if(x($opts,'headers')){
99 @curl_setopt($ch, CURLOPT_HTTPHEADER, $opts['headers']);
101 if(x($opts,'nobody')){
102 @curl_setopt($ch, CURLOPT_NOBODY, $opts['nobody']);
104 if(x($opts,'timeout')){
105 @curl_setopt($ch, CURLOPT_TIMEOUT, $opts['timeout']);
107 $curl_time = intval(get_config('system','curl_timeout'));
108 @curl_setopt($ch, CURLOPT_TIMEOUT, (($curl_time !== false) ? $curl_time : 60));
111 // by default we will allow self-signed certs
112 // but you can override this
114 $check_cert = get_config('system','verifyssl');
115 @curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, (($check_cert) ? true : false));
116 @curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, (($check_cert) ? 2 : false));
118 $prx = get_config('system','proxy');
120 @curl_setopt($ch, CURLOPT_HTTPPROXYTUNNEL, 1);
121 @curl_setopt($ch, CURLOPT_PROXY, $prx);
122 $prxusr = @get_config('system','proxyuser');
124 @curl_setopt($ch, CURLOPT_PROXYUSERPWD, $prxusr);
127 @curl_setopt($ch, CURLOPT_BINARYTRANSFER,1);
129 $a->set_curl_code(0);
131 // don't let curl abort the entire application
132 // if it throws any errors.
134 $s = @curl_exec($ch);
135 if (curl_errno($ch) !== CURLE_OK) {
136 logger('fetch_url error fetching '.$url.': '.curl_error($ch), LOGGER_NORMAL);
140 $curl_info = @curl_getinfo($ch);
142 $http_code = $curl_info['http_code'];
143 logger('fetch_url '.$url.': '.$http_code." ".$s, LOGGER_DATA);
146 // Pull out multiple headers, e.g. proxy and continuation headers
147 // allow for HTTP/2.x without fixing code
149 while(preg_match('/^HTTP\/[1-2].+? [1-5][0-9][0-9]/',$base)) {
150 $chunk = substr($base,0,strpos($base,"\r\n\r\n")+4);
152 $base = substr($base,strlen($chunk));
155 $a->set_curl_code($http_code);
156 $a->set_curl_content_type($curl_info['content_type']);
157 $a->set_curl_headers($header);
159 if($http_code == 301 || $http_code == 302 || $http_code == 303 || $http_code == 307) {
160 $new_location_info = @parse_url($curl_info["redirect_url"]);
161 $old_location_info = @parse_url($curl_info["url"]);
163 $newurl = $curl_info["redirect_url"];
165 if (($new_location_info["path"] == "") AND ($new_location_info["host"] != ""))
166 $newurl = $new_location_info["scheme"]."://".$new_location_info["host"].$old_location_info["path"];
169 if (preg_match('/(Location:|URI:)(.*?)\n/i', $header, $matches)) {
170 $newurl = trim(array_pop($matches));
172 if(strpos($newurl,'/') === 0)
173 $newurl = $old_location_info["scheme"]."://".$old_location_info["host"].$newurl;
174 if (filter_var($newurl, FILTER_VALIDATE_URL)) {
177 return z_fetch_url($newurl,$binary, $redirects, $opts);
182 $a->set_curl_code($http_code);
183 $a->set_curl_content_type($curl_info['content_type']);
185 $body = substr($s,strlen($header));
189 $rc = intval($http_code);
190 $ret['return_code'] = $rc;
191 $ret['success'] = (($rc >= 200 && $rc <= 299) ? true : false);
192 $ret['redirect_url'] = $url;
193 if(! $ret['success']) {
194 $ret['error'] = curl_error($ch);
195 $ret['debug'] = $curl_info;
196 logger('z_fetch_url: error: ' . $url . ': ' . $ret['error'], LOGGER_DEBUG);
197 logger('z_fetch_url: debug: ' . print_r($curl_info,true), LOGGER_DATA);
199 $ret['body'] = substr($s,strlen($header));
200 $ret['header'] = $header;
201 if(x($opts,'debug')) {
202 $ret['debug'] = $curl_info;
206 $a->save_timestamp($stamp1, "network");
212 // post request to $url. $params is an array of post variables.
215 * @brief Post request to $url
217 * @param string $url URL to post
218 * @param mixed $params
219 * @param string $headers HTTP headers
220 * @param integer $redirects Recursion counter for internal use - default = 0
221 * @param integer $timeout The timeout in seconds, default system config value or 60 seconds
223 * @return string The content
225 function post_url($url,$params, $headers = null, &$redirects = 0, $timeout = 0) {
226 $stamp1 = microtime(true);
229 $ch = curl_init($url);
230 if(($redirects > 8) || (! $ch))
233 logger("post_url: start ".$url, LOGGER_DATA);
235 curl_setopt($ch, CURLOPT_HEADER, true);
236 curl_setopt($ch, CURLOPT_RETURNTRANSFER,true);
237 curl_setopt($ch, CURLOPT_POST,1);
238 curl_setopt($ch, CURLOPT_POSTFIELDS,$params);
239 curl_setopt($ch, CURLOPT_USERAGENT, $a->get_useragent());
241 if(intval($timeout)) {
242 curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
245 $curl_time = intval(get_config('system','curl_timeout'));
246 curl_setopt($ch, CURLOPT_TIMEOUT, (($curl_time !== false) ? $curl_time : 60));
249 if(defined('LIGHTTPD')) {
250 if(!is_array($headers)) {
251 $headers = array('Expect:');
253 if(!in_array('Expect:', $headers)) {
254 array_push($headers, 'Expect:');
259 curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
261 $check_cert = get_config('system','verifyssl');
262 curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, (($check_cert) ? true : false));
263 curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, (($check_cert) ? 2 : false));
264 $prx = get_config('system','proxy');
266 curl_setopt($ch, CURLOPT_HTTPPROXYTUNNEL, 1);
267 curl_setopt($ch, CURLOPT_PROXY, $prx);
268 $prxusr = get_config('system','proxyuser');
270 curl_setopt($ch, CURLOPT_PROXYUSERPWD, $prxusr);
273 $a->set_curl_code(0);
275 // don't let curl abort the entire application
276 // if it throws any errors.
278 $s = @curl_exec($ch);
281 $curl_info = curl_getinfo($ch);
282 $http_code = $curl_info['http_code'];
284 logger("post_url: result ".$http_code." - ".$url, LOGGER_DATA);
288 // Pull out multiple headers, e.g. proxy and continuation headers
289 // allow for HTTP/2.x without fixing code
291 while(preg_match('/^HTTP\/[1-2].+? [1-5][0-9][0-9]/',$base)) {
292 $chunk = substr($base,0,strpos($base,"\r\n\r\n")+4);
294 $base = substr($base,strlen($chunk));
297 if($http_code == 301 || $http_code == 302 || $http_code == 303 || $http_code == 307) {
299 preg_match('/(Location:|URI:)(.*?)\n/', $header, $matches);
300 $newurl = trim(array_pop($matches));
301 if(strpos($newurl,'/') === 0)
302 $newurl = $old_location_info["scheme"] . "://" . $old_location_info["host"] . $newurl;
303 if (filter_var($newurl, FILTER_VALIDATE_URL)) {
305 logger("post_url: redirect ".$url." to ".$newurl);
306 return post_url($newurl,$params, $headers, $redirects, $timeout);
307 //return fetch_url($newurl,false,$redirects,$timeout);
310 $a->set_curl_code($http_code);
311 $body = substr($s,strlen($header));
313 $a->set_curl_headers($header);
317 $a->save_timestamp($stamp1, "network");
319 logger("post_url: end ".$url, LOGGER_DATA);
324 // Generic XML return
325 // Outputs a basic dfrn XML status structure to STDOUT, with a <status> variable
326 // of $st and an optional text <message> of $message and terminates the current process.
328 function xml_status($st, $message = '') {
330 $xml_message = ((strlen($message)) ? "\t<message>" . xmlify($message) . "</message>\r\n" : '');
333 logger('xml_status returning non_zero: ' . $st . " message=" . $message);
335 header( "Content-type: text/xml" );
336 echo '<?xml version="1.0" encoding="UTF-8"?>'."\r\n";
337 echo "<result>\r\n\t<status>$st</status>\r\n$xml_message</result>\r\n";
343 * @brief Send HTTP status header and exit.
345 * @param integer $val HTTP status result value
346 * @param array $description optional message
347 * 'title' => header title
348 * 'description' => optional message
351 function http_status_exit($val, $description = array()) {
355 if (!isset($description["title"]))
356 $description["title"] = $err." ".$val;
358 if($val >= 200 && $val < 300)
361 logger('http_status_exit ' . $val);
362 header($_SERVER["SERVER_PROTOCOL"] . ' ' . $val . ' ' . $err);
364 if (isset($description["title"])) {
365 $tpl = get_markup_template('http_status.tpl');
366 echo replace_macros($tpl, array('$title' => $description["title"],
367 '$description' => $description["description"]));
374 // Given an email style address, perform webfinger lookup and
375 // return the resulting DFRN profile URL, or if no DFRN profile URL
376 // is located, returns an OStatus subscription template (prefixed
377 // with the string 'stat:' to identify it as on OStatus template).
378 // If this isn't an email style address just return $webbie.
379 // Return an empty string if email-style addresses but webfinger fails,
380 // or if the resultant personal XRD doesn't contain a supported
381 // subscription/friend-request attribute.
383 // amended 7/9/2011 to return an hcard which could save potentially loading
384 // a lengthy content page to scrape dfrn attributes
386 function webfinger_dfrn($webbie,&$hcard) {
387 if(! strstr($webbie,'@')) {
392 $links = webfinger($webbie);
393 logger('webfinger_dfrn: ' . $webbie . ':' . print_r($links,true), LOGGER_DATA);
395 foreach($links as $link) {
396 if($link['@attributes']['rel'] === NAMESPACE_DFRN)
397 $profile_link = $link['@attributes']['href'];
398 if($link['@attributes']['rel'] === NAMESPACE_OSTATUSSUB)
399 $profile_link = 'stat:' . $link['@attributes']['template'];
400 if($link['@attributes']['rel'] === 'http://microformats.org/profile/hcard')
401 $hcard = $link['@attributes']['href'];
404 return $profile_link;
408 * @brief Perform webfinger lookup on an email style address
410 * @param string $webbi An email style address
411 * @param boolean $debug
413 * @return array of link attributes from the personal XRD file
414 * empty array on error/failure
416 function webfinger($webbie, $debug = false) {
418 if(strstr($webbie,'@')) {
419 $host = substr($webbie,strpos($webbie,'@') + 1);
422 $tpl = fetch_lrdd_template($host);
423 logger('webfinger: lrdd template: ' . $tpl);
425 $pxrd = str_replace('{uri}', urlencode('acct:' . $webbie), $tpl);
426 logger('webfinger: pxrd: ' . $pxrd);
427 $links = fetch_xrd_links($pxrd);
428 if(! count($links)) {
429 // try with double slashes
430 $pxrd = str_replace('{uri}', urlencode('acct://' . $webbie), $tpl);
431 logger('webfinger: pxrd: ' . $pxrd);
432 $links = fetch_xrd_links($pxrd);
440 function lrdd($uri, $debug = false) {
444 // default priority is host priority, host-meta first
448 // All we have is an email address. Resource-priority is irrelevant
449 // because our URI isn't directly resolvable.
451 if(strstr($uri,'@')) {
452 return(webfinger($uri));
455 // get the host meta file
457 $host = @parse_url($uri);
460 $url = ((x($host,'scheme')) ? $host['scheme'] : 'http') . '://';
461 $url .= $host['host'] . '/.well-known/host-meta' ;
466 logger('lrdd: constructed url: ' . $url);
468 $xml = fetch_url($url);
470 $headers = $a->get_curl_headers();
475 logger('lrdd: host_meta: ' . $xml, LOGGER_DATA);
477 if(! stristr($xml,'<xrd'))
480 $h = parse_xml_string($xml);
484 $arr = xml::element_to_array($h);
486 if(isset($arr['xrd']['property'])) {
487 $property = $arr['crd']['property'];
488 if(! isset($property[0]))
489 $properties = array($property);
491 $properties = $property;
492 foreach($properties as $prop)
493 if((string) $prop['@attributes'] === 'http://lrdd.net/priority/resource')
494 $priority = 'resource';
497 // save the links in case we need them
501 if(isset($arr['xrd']['link'])) {
502 $link = $arr['xrd']['link'];
503 if(! isset($link[0]))
504 $links = array($link);
509 // do we have a template or href?
512 foreach($links as $link) {
513 if($link['@attributes']['rel'] && attribute_contains($link['@attributes']['rel'],'lrdd')) {
514 if(x($link['@attributes'],'template'))
515 $tpl = $link['@attributes']['template'];
516 elseif(x($link['@attributes'],'href'))
517 $href = $link['@attributes']['href'];
522 if((! isset($tpl)) || (! strpos($tpl,'{uri}')))
525 if($priority === 'host') {
527 $pxrd = str_replace('{uri}', urlencode($uri), $tpl);
531 logger('lrdd: (host priority) pxrd: ' . $pxrd);
532 $links = fetch_xrd_links($pxrd);
536 $lines = explode("\n",$headers);
538 foreach($lines as $line) {
539 if((stristr($line,'link:')) && preg_match('/<([^>].*)>.*rel\=[\'\"]lrdd[\'\"]/',$line,$matches)) {
540 return(fetch_xrd_links($matches[1]));
548 // priority 'resource'
551 $html = fetch_url($uri);
552 $headers = $a->get_curl_headers();
553 logger('lrdd: headers=' . $headers, LOGGER_DEBUG);
555 // don't try and parse raw xml as html
556 if(! strstr($html,'<?xml')) {
557 require_once('library/HTML5/Parser.php');
560 $dom = HTML5_Parser::parse($html);
561 } catch (DOMException $e) {
562 logger('lrdd: parse error: ' . $e);
565 if(isset($dom) && $dom) {
566 $items = $dom->getElementsByTagName('link');
567 foreach($items as $item) {
568 $x = $item->getAttribute('rel');
570 $pagelink = $item->getAttribute('href');
578 return(fetch_xrd_links($pagelink));
580 // next look in HTTP headers
582 $lines = explode("\n",$headers);
584 foreach($lines as $line) {
585 /// @TODO Alter the following regex to support multiple relations (space separated)
586 if((stristr($line,'link:')) && preg_match('/<([^>].*)>.*rel\=[\'\"]lrdd[\'\"]/',$line,$matches)) {
587 $pagelink = $matches[1];
590 // don't try and run feeds through the html5 parser
591 if(stristr($line,'content-type:') && ((stristr($line,'application/atom+xml')) || (stristr($line,'application/rss+xml'))))
593 if(stristr($html,'<rss') || stristr($html,'<feed'))
599 return(fetch_xrd_links($pagelink));
601 // If we haven't found any links, return the host xrd links (which we have already fetched)
610 // Given a host name, locate the LRDD template from that
611 // host. Returns the LRDD template or an empty string on
614 function fetch_lrdd_template($host) {
617 $url1 = 'https://' . $host . '/.well-known/host-meta' ;
618 $url2 = 'http://' . $host . '/.well-known/host-meta' ;
619 $links = fetch_xrd_links($url1);
620 logger('fetch_lrdd_template from: ' . $url1);
621 logger('template (https): ' . print_r($links,true));
622 if(! count($links)) {
623 logger('fetch_lrdd_template from: ' . $url2);
624 $links = fetch_xrd_links($url2);
625 logger('template (http): ' . print_r($links,true));
628 foreach($links as $link)
629 if($link['@attributes']['rel'] && $link['@attributes']['rel'] === 'lrdd' && (!$link['@attributes']['type'] || $link['@attributes']['type'] === 'application/xrd+xml'))
630 $tpl = $link['@attributes']['template'];
632 if(! strpos($tpl,'{uri}'))
638 * @brief Given a URL, retrieve the page as an XRD document.
640 * @param string $url An url
641 * @return array of links
642 * return empty array on error/failure
644 function fetch_xrd_links($url) {
646 $xrd_timeout = intval(get_config('system','xrd_timeout'));
648 $xml = fetch_url($url,false,$redirects,(($xrd_timeout) ? $xrd_timeout : 20), "application/xrd+xml");
650 logger('fetch_xrd_links: ' . $xml, LOGGER_DATA);
652 if ((! $xml) || (! stristr($xml,'<xrd')))
655 // fix diaspora's bad xml
656 $xml = str_replace(array('href="','"/>'),array('href="','"/>'),$xml);
658 $h = parse_xml_string($xml);
662 $arr = xml::element_to_array($h);
666 if(isset($arr['xrd']['link'])) {
667 $link = $arr['xrd']['link'];
668 if(! isset($link[0]))
669 $links = array($link);
673 if(isset($arr['xrd']['alias'])) {
674 $alias = $arr['xrd']['alias'];
675 if(! isset($alias[0]))
676 $aliases = array($alias);
679 if(is_array($aliases) && count($aliases)) {
680 foreach($aliases as $alias) {
681 $links[]['@attributes'] = array('rel' => 'alias' , 'href' => $alias);
686 logger('fetch_xrd_links: ' . print_r($links,true), LOGGER_DATA);
693 * @brief Check URL to se if ts's real
695 * Take a URL from the wild, prepend http:// if necessary
696 * and check DNS to see if it's real (or check if is a valid IP address)
698 * @param string $url The URL to be validated
699 * @return boolean True if it's a valid URL, fals if something wrong with it
701 function validate_url(&$url) {
703 if(get_config('system','disable_url_validation'))
705 // no naked subdomains (allow localhost for tests)
706 if(strpos($url,'.') === false && strpos($url,'/localhost/') === false)
708 if(substr($url,0,4) != 'http')
709 $url = 'http://' . $url;
710 $h = @parse_url($url);
712 if(($h) && (dns_get_record($h['host'], DNS_A + DNS_CNAME + DNS_PTR) || filter_var($h['host'], FILTER_VALIDATE_IP) )) {
719 * @brief Checks that email is an actual resolvable internet address
721 * @param string $addr The email address
722 * @return boolean True if it's a valid email address, false if it's not
724 function validate_email($addr) {
726 if(get_config('system','disable_email_validation'))
729 if(! strpos($addr,'@'))
731 $h = substr($addr,strpos($addr,'@') + 1);
733 if(($h) && (dns_get_record($h, DNS_A + DNS_CNAME + DNS_PTR + DNS_MX) || filter_var($h, FILTER_VALIDATE_IP) )) {
740 * @brief Check if URL is allowed
742 * Check $url against our list of allowed sites,
743 * wildcards allowed. If allowed_sites is unset return true;
745 * @param string $url URL which get tested
746 * @return boolean True if url is allowed otherwise return false
748 function allowed_url($url) {
750 $h = @parse_url($url);
756 $str_allowed = get_config('system','allowed_sites');
762 $host = strtolower($h['host']);
764 // always allow our own site
766 if($host == strtolower($_SERVER['SERVER_NAME']))
769 $fnmatch = function_exists('fnmatch');
770 $allowed = explode(',',$str_allowed);
772 if(count($allowed)) {
773 foreach($allowed as $a) {
774 $pat = strtolower(trim($a));
775 if(($fnmatch && fnmatch($pat,$host)) || ($pat == $host)) {
785 * @brief Check if email address is allowed to register here.
787 * Compare against our list (wildcards allowed).
790 * @return boolean False if not allowed, true if allowed
791 * or if allowed list is not configured
793 function allowed_email($email) {
796 $domain = strtolower(substr($email,strpos($email,'@') + 1));
800 $str_allowed = get_config('system','allowed_email');
806 $fnmatch = function_exists('fnmatch');
807 $allowed = explode(',',$str_allowed);
809 if(count($allowed)) {
810 foreach($allowed as $a) {
811 $pat = strtolower(trim($a));
812 if(($fnmatch && fnmatch($pat,$domain)) || ($pat == $domain)) {
821 function avatar_img($email) {
825 $avatar['size'] = 175;
826 $avatar['email'] = $email;
828 $avatar['success'] = false;
830 call_hooks('avatar_lookup', $avatar);
832 if(! $avatar['success'])
833 $avatar['url'] = $a->get_baseurl() . '/images/person-175.jpg';
835 logger('Avatar: ' . $avatar['email'] . ' ' . $avatar['url'], LOGGER_DEBUG);
836 return $avatar['url'];
840 function parse_xml_string($s,$strict = true) {
841 /// @todo Move this function to the xml class
843 if(! strstr($s,'<?xml'))
845 $s2 = substr($s,strpos($s,'<?xml'));
849 libxml_use_internal_errors(true);
851 $x = @simplexml_load_string($s2);
853 logger('libxml: parse: error: ' . $s2, LOGGER_DATA);
854 foreach(libxml_get_errors() as $err)
855 logger('libxml: parse: ' . $err->code." at ".$err->line.":".$err->column." : ".$err->message, LOGGER_DATA);
856 libxml_clear_errors();
861 function scale_external_images($srctext, $include_link = true, $scale_replace = false) {
863 // Suppress "view full size"
864 if (intval(get_config('system','no_view_full_size')))
865 $include_link = false;
869 // Picture addresses can contain special characters
870 $s = htmlspecialchars_decode($srctext);
873 $c = preg_match_all('/\[img.*?\](.*?)\[\/img\]/ism',$s,$matches,PREG_SET_ORDER);
875 require_once('include/Photo.php');
876 foreach($matches as $mtch) {
877 logger('scale_external_image: ' . $mtch[1]);
879 $hostname = str_replace('www.','',substr($a->get_baseurl(),strpos($a->get_baseurl(),'://')+3));
880 if(stristr($mtch[1],$hostname))
883 // $scale_replace, if passed, is an array of two elements. The
884 // first is the name of the full-size image. The second is the
885 // name of a remote, scaled-down version of the full size image.
886 // This allows Friendica to display the smaller remote image if
887 // one exists, while still linking to the full-size image
889 $scaled = str_replace($scale_replace[0], $scale_replace[1], $mtch[1]);
892 $i = @fetch_url($scaled);
896 // guess mimetype from headers or filename
897 $type = guess_image_type($mtch[1],true);
900 $ph = new Photo($i, $type);
901 if($ph->is_valid()) {
902 $orig_width = $ph->getWidth();
903 $orig_height = $ph->getHeight();
905 if($orig_width > 640 || $orig_height > 640) {
907 $ph->scaleImage(640);
908 $new_width = $ph->getWidth();
909 $new_height = $ph->getHeight();
910 logger('scale_external_images: ' . $orig_width . '->' . $new_width . 'w ' . $orig_height . '->' . $new_height . 'h' . ' match: ' . $mtch[0], LOGGER_DEBUG);
911 $s = str_replace($mtch[0],'[img=' . $new_width . 'x' . $new_height. ']' . $scaled . '[/img]'
912 . "\n" . (($include_link)
913 ? '[url=' . $mtch[1] . ']' . t('view full size') . '[/url]' . "\n"
915 logger('scale_external_images: new string: ' . $s, LOGGER_DEBUG);
922 // replace the special char encoding
923 $s = htmlspecialchars($s,ENT_NOQUOTES,'UTF-8');
928 function fix_contact_ssl_policy(&$contact,$new_policy) {
930 $ssl_changed = false;
931 if((intval($new_policy) == SSL_POLICY_SELFSIGN || $new_policy === 'self') && strstr($contact['url'],'https:')) {
933 $contact['url'] = str_replace('https:','http:',$contact['url']);
934 $contact['request'] = str_replace('https:','http:',$contact['request']);
935 $contact['notify'] = str_replace('https:','http:',$contact['notify']);
936 $contact['poll'] = str_replace('https:','http:',$contact['poll']);
937 $contact['confirm'] = str_replace('https:','http:',$contact['confirm']);
938 $contact['poco'] = str_replace('https:','http:',$contact['poco']);
941 if((intval($new_policy) == SSL_POLICY_FULL || $new_policy === 'full') && strstr($contact['url'],'http:')) {
943 $contact['url'] = str_replace('http:','https:',$contact['url']);
944 $contact['request'] = str_replace('http:','https:',$contact['request']);
945 $contact['notify'] = str_replace('http:','https:',$contact['notify']);
946 $contact['poll'] = str_replace('http:','https:',$contact['poll']);
947 $contact['confirm'] = str_replace('http:','https:',$contact['confirm']);
948 $contact['poco'] = str_replace('http:','https:',$contact['poco']);
952 q("update contact set
959 where id = %d limit 1",
960 dbesc($contact['url']),
961 dbesc($contact['request']),
962 dbesc($contact['notify']),
963 dbesc($contact['poll']),
964 dbesc($contact['confirm']),
965 dbesc($contact['poco']),
966 intval($contact['id'])
971 function original_url($url, $depth=1, $fetchbody = false) {
975 // Remove Analytics Data from Google and other tracking platforms
976 $urldata = parse_url($url);
977 if (is_string($urldata["query"])) {
978 $query = $urldata["query"];
979 parse_str($query, $querydata);
981 if (is_array($querydata))
982 foreach ($querydata AS $param=>$value)
983 if (in_array($param, array("utm_source", "utm_medium", "utm_term", "utm_content", "utm_campaign",
984 "wt_mc", "pk_campaign", "pk_kwd", "mc_cid", "mc_eid",
985 "fb_action_ids", "fb_action_types", "fb_ref",
987 "woo_campaign", "woo_source", "woo_medium", "woo_content", "woo_term"))) {
989 $pair = $param."=".urlencode($value);
990 $url = str_replace($pair, "", $url);
992 // Second try: if the url isn't encoded completely
993 $pair = $param."=".str_replace(" ", "+", $value);
994 $url = str_replace($pair, "", $url);
996 // Third try: Maybey the url isn't encoded at all
997 $pair = $param."=".$value;
998 $url = str_replace($pair, "", $url);
1000 $url = str_replace(array("?&", "&&"), array("?", ""), $url);
1003 if (substr($url, -1, 1) == "?")
1004 $url = substr($url, 0, -1);
1010 $url = trim($url, "'");
1012 $stamp1 = microtime(true);
1014 $siteinfo = array();
1016 curl_setopt($ch, CURLOPT_URL, $url);
1017 curl_setopt($ch, CURLOPT_HEADER, 1);
1018 curl_setopt($ch, CURLOPT_NOBODY, 1);
1019 curl_setopt($ch, CURLOPT_TIMEOUT, 10);
1020 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
1021 curl_setopt($ch, CURLOPT_USERAGENT, $a->get_useragent());
1023 $header = curl_exec($ch);
1024 $curl_info = @curl_getinfo($ch);
1025 $http_code = $curl_info['http_code'];
1028 $a->save_timestamp($stamp1, "network");
1030 if ($http_code == 0)
1033 if ((($curl_info['http_code'] == "301") OR ($curl_info['http_code'] == "302"))
1034 AND (($curl_info['redirect_url'] != "") OR ($curl_info['location'] != ""))) {
1035 if ($curl_info['redirect_url'] != "")
1036 return(original_url($curl_info['redirect_url'], ++$depth, $fetchbody));
1038 return(original_url($curl_info['location'], ++$depth, $fetchbody));
1041 // Check for redirects in the meta elements of the body if there are no redirects in the header.
1043 return(original_url($url, ++$depth, true));
1045 // if the file is too large then exit
1046 if ($curl_info["download_content_length"] > 1000000)
1049 // if it isn't a HTML file then exit
1050 if (($curl_info["content_type"] != "") AND !strstr(strtolower($curl_info["content_type"]),"html"))
1053 $stamp1 = microtime(true);
1056 curl_setopt($ch, CURLOPT_URL, $url);
1057 curl_setopt($ch, CURLOPT_HEADER, 0);
1058 curl_setopt($ch, CURLOPT_NOBODY, 0);
1059 curl_setopt($ch, CURLOPT_TIMEOUT, 10);
1060 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
1061 curl_setopt($ch, CURLOPT_USERAGENT, $a->get_useragent());
1063 $body = curl_exec($ch);
1066 $a->save_timestamp($stamp1, "network");
1068 if (trim($body) == "")
1071 // Check for redirect in meta elements
1072 $doc = new DOMDocument();
1073 @$doc->loadHTML($body);
1075 $xpath = new DomXPath($doc);
1077 $list = $xpath->query("//meta[@content]");
1078 foreach ($list as $node) {
1080 if ($node->attributes->length)
1081 foreach ($node->attributes as $attribute)
1082 $attr[$attribute->name] = $attribute->value;
1084 if (@$attr["http-equiv"] == 'refresh') {
1085 $path = $attr["content"];
1086 $pathinfo = explode(";", $path);
1088 foreach ($pathinfo AS $value)
1089 if (substr(strtolower($value), 0, 4) == "url=")
1090 return(original_url(substr($value, 4), ++$depth));
1097 function short_link($url) {
1098 require_once('library/slinky.php');
1099 $slinky = new Slinky($url);
1100 $yourls_url = get_config('yourls','url1');
1102 $yourls_username = get_config('yourls','username1');
1103 $yourls_password = get_config('yourls', 'password1');
1104 $yourls_ssl = get_config('yourls', 'ssl1');
1105 $yourls = new Slinky_YourLS();
1106 $yourls->set('username', $yourls_username);
1107 $yourls->set('password', $yourls_password);
1108 $yourls->set('ssl', $yourls_ssl);
1109 $yourls->set('yourls-url', $yourls_url);
1110 $slinky->set_cascade( array($yourls, new Slinky_UR1ca(), new Slinky_Trim(), new Slinky_IsGd(), new Slinky_TinyURL()));
1112 // setup a cascade of shortening services
1113 // try to get a short link from these services
1114 // in the order ur1.ca, trim, id.gd, tinyurl
1115 $slinky->set_cascade(array(new Slinky_UR1ca(), new Slinky_Trim(), new Slinky_IsGd(), new Slinky_TinyURL()));
1117 return $slinky->short();
1121 * @brief Encodes content to json
1123 * This function encodes an array to json format
1124 * and adds an application/json HTTP header to the output.
1125 * After finishing the process is getting killed.
1127 * @param array $x The input content
1129 function json_return_and_die($x) {
1130 header("content-type: application/json");
1131 echo json_encode($x);