<?php
/**
* @file include/network.php
*/
/**
* @brief Returns path to CA file.
*
* @return string
*/
function get_capath() {
return appdirpath() . '/library/cacert.pem';
}
/**
* @brief fetches an URL.
*
* @param string $url
* URL to fetch
* @param boolean $binary default false
* TRUE if asked to return binary results (file download)
* @param int $redirects default 0
* internal use, recursion counter
* @param array $opts (optional parameters) associative array with:
* * \b accept_content => supply Accept: header with 'accept_content' as the value
* * \b timeout => int seconds, default system config value or 60 seconds
* * \b http_auth => username:password
* * \b novalidate => do not validate SSL certs, default is to validate using our CA list
* * \b nobody => only return the header
* * \b filep => stream resource to write body to. header and body are not returned when using this option.
* * \b custom => custom request method: e.g. 'PUT', 'DELETE'
* * \b cookiejar => cookie file (write)
* * \b cookiefile => cookie file (read)
*
* @return array an associative array with:
* * \e int \b return_code => HTTP return code or 0 if timeout or failure
* * \e boolean \b success => boolean true (if HTTP 2xx result) or false
* * \e string \b header => HTTP headers
* * \e string \b body => fetched content
*/
function z_fetch_url($url, $binary = false, $redirects = 0, $opts = array()) {
$ret = array('return_code' => 0, 'success' => false, 'header' => "", 'body' => "");
$ch = @curl_init($url);
if(($redirects > 8) || (! $ch))
return $ret;
@curl_setopt($ch, CURLOPT_HEADER, true);
@curl_setopt($ch, CURLINFO_HEADER_OUT, true);
@curl_setopt($ch, CURLOPT_CAINFO, get_capath());
@curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
@curl_setopt($ch, CURLOPT_RETURNTRANSFER,true);
@curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/5.0 (compatible; zot)");
$ciphers = @get_config('system','curl_ssl_ciphers');
if($ciphers)
@curl_setopt($ch, CURLOPT_SSL_CIPHER_LIST, $ciphers);
if(x($opts,'filep')) {
@curl_setopt($ch, CURLOPT_FILE, $opts['filep']);
@curl_setopt($ch, CURLOPT_HEADER, false);
}
if(x($opts,'upload'))
@curl_setopt($ch, CURLOPT_UPLOAD, $opts['upload']);
if(x($opts,'infile'))
@curl_setopt($ch, CURLOPT_INFILE, $opts['infile']);
if(x($opts,'infilesize'))
@curl_setopt($ch, CURLOPT_INFILESIZE, $opts['infilesize']);
if(x($opts,'readfunc'))
@curl_setopt($ch, CURLOPT_READFUNCTION, $opts['readfunc']);
if(x($opts,'headers'))
@curl_setopt($ch, CURLOPT_HTTPHEADER, $opts['headers']);
if(x($opts,'nobody'))
@curl_setopt($ch, CURLOPT_NOBODY, $opts['nobody']);
if(x($opts,'custom'))
@curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $opts['custom']);
if(x($opts,'timeout') && intval($opts['timeout'])) {
@curl_setopt($ch, CURLOPT_TIMEOUT, intval($opts['timeout']));
}
else {
$curl_time = intval(@get_config('system','curl_timeout'));
@curl_setopt($ch, CURLOPT_TIMEOUT, (($curl_time !== false) ? $curl_time : 60));
}
if(x($opts,'http_auth')) {
// "username" . ':' . "password"
@curl_setopt($ch, CURLOPT_USERPWD, $opts['http_auth']);
}
if(x($opts,'cookiejar'))
@curl_setopt($ch, CURLOPT_COOKIEJAR, $opts['cookiejar']);
if(x($opts,'cookiefile'))
@curl_setopt($ch, CURLOPT_COOKIEFILE, $opts['cookiefile']);
if(x($opts,'cookie'))
@curl_setopt($ch, CURLOPT_COOKIE, $opts['cookie']);
@curl_setopt($ch, CURLOPT_SSL_VERIFYPEER,
((x($opts,'novalidate') && intval($opts['novalidate'])) ? false : true));
$prx = @get_config('system','proxy');
if(strlen($prx)) {
@curl_setopt($ch, CURLOPT_HTTPPROXYTUNNEL, 1);
@curl_setopt($ch, CURLOPT_PROXY, $prx);
$prxusr = @get_config('system','proxyuser');
if(strlen($prxusr))
@curl_setopt($ch, CURLOPT_PROXYUSERPWD, $prxusr);
}
if($binary)
@curl_setopt($ch, CURLOPT_BINARYTRANSFER,1);
// don't let curl abort the entire application'
// if it throws any errors.
$s = @curl_exec($ch);
$base = $s;
$curl_info = @curl_getinfo($ch);
$http_code = $curl_info['http_code'];
//logger('fetch_url:' . $http_code . ' data: ' . $s);
$header = '';
// Pull out multiple headers, e.g. proxy and continuation headers
// allow for HTTP/2.x without fixing code
while(preg_match('/^HTTP\/[1-2].+? [1-5][0-9][0-9]/',$base)) {
$chunk = substr($base,0,strpos($base,"\r\n\r\n")+4);
$header .= $chunk;
$base = substr($base,strlen($chunk));
}
if($http_code == 301 || $http_code == 302 || $http_code == 303 || $http_code == 307 || $http_code == 308) {
$matches = array();
preg_match('/(Location:|URI:)(.*?)\n/', $header, $matches);
$newurl = trim(array_pop($matches));
if(strpos($newurl,'/') === 0)
$newurl = $url . $newurl;
$url_parsed = @parse_url($newurl);
if (isset($url_parsed)) {
@curl_close($ch);
return z_fetch_url($newurl,$binary,++$redirects,$opts);
}
}
$rc = intval($http_code);
$ret['return_code'] = $rc;
$ret['success'] = (($rc >= 200 && $rc <= 299) ? true : false);
if(! $ret['success']) {
$ret['error'] = curl_error($ch);
$ret['debug'] = $curl_info;
logger('z_fetch_url: error: ' . $url . ': ' . $ret['error'], LOGGER_DEBUG);
logger('z_fetch_url: debug: ' . print_r($curl_info,true), LOGGER_DATA);
}
$ret['body'] = substr($s,strlen($header));
$ret['header'] = $header;
if(x($opts,'debug')) {
$ret['debug'] = $curl_info;
}
@curl_close($ch);
return($ret);
}
/**
* @brief
*
* @param string $url
* URL to post
* @param mixed $params
* The full data to post in a HTTP "POST" operation. This parameter can
* either be passed as a urlencoded string like 'para1=val1¶2=val2&...'
* or as an array with the field name as key and field data as value. If value
* is an array, the Content-Type header will be set to multipart/form-data.
* @param int $redirects = 0
* internal use, recursion counter
* @param array $opts (optional parameters)
* 'accept_content' => supply Accept: header with 'accept_content' as the value
* 'timeout' => int seconds, default system config value or 60 seconds
* 'http_auth' => username:password
* 'novalidate' => do not validate SSL certs, default is to validate using our CA list
* 'filep' => stream resource to write body to. header and body are not returned when using this option.
* 'custom' => custom request method: e.g. 'PUT', 'DELETE'
*
* @return array an associative array with:
* * \e int \b return_code => HTTP return code or 0 if timeout or failure
* * \e boolean \b success => boolean true (if HTTP 2xx result) or false
* * \e string \b header => HTTP headers
* * \e string \b body => content
* * \e string \b debug => from curl_info()
*/
function z_post_url($url,$params, $redirects = 0, $opts = array()) {
// logger('url: ' . $url);
// logger('params: ' . print_r($params,true));
// logger('opts: ' . print_r($opts,true));
$ret = array('return_code' => 0, 'success' => false, 'header' => "", 'body' => "");
$ch = curl_init($url);
if(($redirects > 8) || (! $ch))
return $ret;
@curl_setopt($ch, CURLOPT_HEADER, true);
@curl_setopt($ch, CURLINFO_HEADER_OUT, true);
@curl_setopt($ch, CURLOPT_CAINFO, get_capath());
@curl_setopt($ch, CURLOPT_RETURNTRANSFER,true);
@curl_setopt($ch, CURLOPT_POST,1);
@curl_setopt($ch, CURLOPT_POSTFIELDS,$params);
@curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/5.0 (compatible; zot)");
$ciphers = @get_config('system','curl_ssl_ciphers');
if($ciphers)
@curl_setopt($ch, CURLOPT_SSL_CIPHER_LIST, $ciphers);
if(x($opts,'filep')) {
@curl_setopt($ch, CURLOPT_FILE, $opts['filep']);
@curl_setopt($ch, CURLOPT_HEADER, false);
}
if(x($opts,'headers')) {
@curl_setopt($ch, CURLOPT_HTTPHEADER, $opts['headers']);
}
if(x($opts,'nobody'))
@curl_setopt($ch, CURLOPT_NOBODY, $opts['nobody']);
if(x($opts,'custom')) {
@curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $opts['custom']);
@curl_setopt($ch, CURLOPT_POST,0);
}
if(x($opts,'timeout') && intval($opts['timeout'])) {
@curl_setopt($ch, CURLOPT_TIMEOUT, $opts['timeout']);
}
else {
$curl_time = intval(@get_config('system','curl_timeout'));
@curl_setopt($ch, CURLOPT_TIMEOUT, (($curl_time !== false) ? $curl_time : 60));
}
if(x($opts,'http_auth')) {
// "username" . ':' . "password"
@curl_setopt($ch, CURLOPT_USERPWD, $opts['http_auth']);
}
if(x($opts,'cookiejar'))
@curl_setopt($ch, CURLOPT_COOKIEJAR, $opts['cookiejar']);
if(x($opts,'cookiefile'))
@curl_setopt($ch, CURLOPT_COOKIEFILE, $opts['cookiefile']);
if(x($opts,'cookie'))
@curl_setopt($ch, CURLOPT_COOKIE, $opts['cookie']);
@curl_setopt($ch, CURLOPT_SSL_VERIFYPEER,
((x($opts,'novalidate') && intval($opts['novalidate'])) ? false : true));
$prx = get_config('system','proxy');
if(strlen($prx)) {
@curl_setopt($ch, CURLOPT_HTTPPROXYTUNNEL, 1);
@curl_setopt($ch, CURLOPT_PROXY, $prx);
$prxusr = get_config('system','proxyuser');
if(strlen($prxusr))
@curl_setopt($ch, CURLOPT_PROXYUSERPWD, $prxusr);
}
// don't let curl abort the entire application
// if it throws any errors.
$s = @curl_exec($ch);
$base = $s;
$curl_info = @curl_getinfo($ch);
$http_code = $curl_info['http_code'];
$header = '';
// Pull out multiple headers, e.g. proxy and continuation headers
// allow for HTTP/2.x without fixing code
while(preg_match('/^HTTP\/[1-2].+? [1-5][0-9][0-9]/',$base)) {
$chunk = substr($base,0,strpos($base,"\r\n\r\n")+4);
$header .= $chunk;
$base = substr($base,strlen($chunk));
}
// would somebody take lighttpd and just shoot it?
if($http_code == 417) {
curl_close($ch);
if($opts) {
if($opts['headers'])
$opts['headers'][] = 'Expect:';
else
$opts['headers'] = array('Expect:');
}
else
$opts = array('headers' => array('Expect:'));
return z_post_url($url,$params,++$redirects,$opts);
}
if($http_code == 301 || $http_code == 302 || $http_code == 303 || $http_code == 307 || $http_code == 308) {
$matches = array();
preg_match('/(Location:|URI:)(.*?)\n/', $header, $matches);
$newurl = trim(array_pop($matches));
if(strpos($newurl,'/') === 0)
$newurl = $url . $newurl;
$url_parsed = @parse_url($newurl);
if (isset($url_parsed)) {
curl_close($ch);
if($http_code == 303) {
return z_fetch_url($newurl,false,$redirects++,$opts);
} else {
return z_post_url($newurl,$params,++$redirects,$opts);
}
}
}
$rc = intval($http_code);
$ret['return_code'] = $rc;
$ret['success'] = (($rc >= 200 && $rc <= 299) ? true : false);
if(! $ret['success']) {
$ret['error'] = curl_error($ch);
$ret['debug'] = $curl_info;
logger('z_post_url: error: ' . $url . ': ' . $ret['error'], LOGGER_DEBUG);
logger('z_post_url: debug: ' . print_r($curl_info,true), LOGGER_DATA);
}
$ret['body'] = substr($s, strlen($header));
$ret['header'] = $header;
if(x($opts,'debug')) {
$ret['debug'] = $curl_info;
}
curl_close($ch);
return($ret);
}
/**
* @brief Like z_post_url() but with an application/json HTTP header.
*
* Add a "Content-Type: application/json" HTTP-header to $opts and call z_post_url().
*
* @see z_post_url()
*
* @param string $url
* @param array $params
* @param number $redirects default 0
* @param array $opts (optional) curl options
* @return z_post_url()
*/
function z_post_url_json($url, $params, $redirects = 0, $opts = array()) {
$opts = array_merge($opts, array('headers' => array('Content-Type: application/json')));
return z_post_url($url,json_encode($params),$redirects,$opts);
}
function json_return_and_die($x, $content_type = 'application/json') {
header("Content-type: $content_type");
echo json_encode($x);
killme();
}
// Generic XML return
// Outputs a basic dfrn XML status structure to STDOUT, with a <status> variable
// of $st and an optional text <message> of $message and terminates the current process.
function xml_status($st, $message = '') {
$xml_message = ((strlen($message)) ? "\t<message>" . xmlify($message) . "</message>\r\n" : '');
if($st)
logger('xml_status returning non_zero: ' . $st . " message=" . $message);
header( "Content-type: text/xml" );
echo '<?xml version="1.0" encoding="UTF-8"?>'."\r\n";
echo "<result>\r\n\t<status>$st</status>\r\n$xml_message</result>\r\n";
killme();
}
/**
* @brief Send HTTP status header
*
* @param int $val
* integer HTTP status result value
* @param string $msg
* optional message
* @returns nil
*/
function http_status($val, $msg = '') {
if ($val >= 400)
$msg = (($msg) ? $msg : 'Error');
if ($val >= 200 && $val < 300)
$msg = (($msg) ? $msg : 'OK');
logger('http_status_exit ' . $val . ' ' . $msg);
header($_SERVER['SERVER_PROTOCOL'] . ' ' . $val . ' ' . $msg);
}
/**
* @brief Send HTTP status header and exit.
*
* @param int $val
* integer HTTP status result value
* @param string $msg
* optional message
* @returns (does not return, process is terminated)
*/
function http_status_exit($val, $msg = '') {
http_status($val, $msg);
killme();
}
// convert an XML document to a normalised, case-corrected array
// used by webfinger
function convert_xml_element_to_array($xml_element, &$recursion_depth=0) {
// If we're getting too deep, bail out
if ($recursion_depth > 512) {
return(null);
}
if (!is_string($xml_element) &&
!is_array($xml_element) &&
(get_class($xml_element) == 'SimpleXMLElement')) {
$xml_element_copy = $xml_element;
$xml_element = get_object_vars($xml_element);
}
if (is_array($xml_element)) {
$result_array = array();
if (count($xml_element) <= 0) {
return (trim(strval($xml_element_copy)));
}
foreach($xml_element as $key=>$value) {
$recursion_depth++;
$result_array[strtolower($key)] =
convert_xml_element_to_array($value, $recursion_depth);
$recursion_depth--;
}
if ($recursion_depth == 0) {
$temp_array = $result_array;
$result_array = array(
strtolower($xml_element_copy->getName()) => $temp_array,
);
}
return ($result_array);
} else {
return (trim(strval($xml_element)));
}
}
function z_dns_check($h,$check_mx = 0) {
// dns_get_record() has issues on some platforms
// so allow somebody to ignore it completely
// Use config values from memory as this can be called during setup
// before a database or even any config structure exists.
if(is_array(\App::$config) && array_key_exists('system',\App::$config)
&& is_array(\App::$config['system'])
&& array_key_exists('do_not_check_dns',\App::$config['system'])
&& \App::$config['system']['do_not_check_dns'])
return true;
//$opts = DNS_A + DNS_CNAME + DNS_PTR;
//if($check_mx)
// $opts += DNS_MX;
// Specific record type flags are unreliable on FreeBSD and Mac,
// so now we'll ignore these and just check for the existence of any DNS record.
return((@dns_get_record($h) || filter_var($h, FILTER_VALIDATE_IP)) ? true : false);
}
// Take a URL from the wild, prepend http:// if necessary
// and check DNS to see if it's real (or check if is a valid IP address)
// return true if it's OK, false if something is wrong with it
function validate_url(&$url) {
// no naked subdomains (allow localhost for tests)
if(strpos($url,'.') === false && strpos($url,'/localhost/') === false)
return false;
if(substr($url,0,4) != 'http')
$url = 'http://' . $url;
$h = @parse_url($url);
if(($h) && z_dns_check($h['host'])) {
return true;
}
return false;
}
// checks that email is an actual resolvable internet address
function validate_email($addr) {
if(get_config('system','disable_email_validation'))
return true;
if(! strpos($addr,'@'))
return false;
$h = substr($addr,strpos($addr,'@') + 1);
if(($h) && z_dns_check($h,true)) {
return true;
}
return false;
}
// Check $url against our list of allowed sites,
// wildcards allowed. If allowed_sites is unset return true;
// If url is allowed, return true.
// otherwise, return false
function allowed_url($url) {
$h = @parse_url($url);
if(! $h) {
return false;
}
$str_allowed = get_config('system','allowed_sites');
if(! $str_allowed)
return true;
$found = false;
$host = strtolower($h['host']);
// always allow our own site
if($host == strtolower($_SERVER['SERVER_NAME']))
return true;
$fnmatch = function_exists('fnmatch');
$allowed = explode(',',$str_allowed);
if(count($allowed)) {
foreach($allowed as $a) {
$pat = strtolower(trim($a));
if(($fnmatch && fnmatch($pat,$host)) || ($pat == $host)) {
$found = true;
break;
}
}
}
return $found;
}
// check if email address is allowed to register here.
// Compare against our list (wildcards allowed).
// Returns false if not allowed, true if allowed or if
// allowed list is not configured.
function allowed_email($email) {
$domain = strtolower(substr($email,strpos($email,'@') + 1));
if(! $domain)
return false;
$str_allowed = get_config('system','allowed_email');
$str_not_allowed = get_config('system','not_allowed_email');
if(! $str_allowed && ! $str_not_allowed)
return true;
$return = false;
$found_allowed = false;
$found_not_allowed = false;
$fnmatch = function_exists('fnmatch');
$allowed = explode(',',$str_allowed);
if(count($allowed)) {
foreach($allowed as $a) {
$pat = strtolower(trim($a));
if(($fnmatch && fnmatch($pat,$email)) || ($pat == $domain)) {
$found_allowed = true;
break;
}
}
}
$not_allowed = explode(',',$str_not_allowed);
if(count($not_allowed)) {
foreach($not_allowed as $na) {
$pat = strtolower(trim($na));
if(($fnmatch && fnmatch($pat,$email)) || ($pat == $domain)) {
$found_not_allowed = true;
break;
}
}
}
if ($found_allowed) {
$return = true;
} elseif (!$str_allowed && !$found_not_allowed) {
$return = true;
}
return $return;
}
function parse_xml_string($s,$strict = true) {
if($strict) {
if(! strstr($s,'<?xml'))
return false;
$s2 = substr($s,strpos($s,'<?xml'));
}
else
$s2 = $s;
libxml_use_internal_errors(true);
$x = @simplexml_load_string($s2);
if(! $x) {
logger('libxml: parse: error: ' . $s2, LOGGER_DATA);
foreach(libxml_get_errors() as $err)
logger('libxml: parse: ' . $err->code." at ".$err->line.":".$err->column." : ".$err->message, LOGGER_DATA);
libxml_clear_errors();
}
return $x;
}
function scale_external_images($s, $include_link = true, $scale_replace = false) {
// Picture addresses can contain special characters
$s = htmlspecialchars_decode($s, ENT_COMPAT);
$matches = null;
$c = preg_match_all('/\[([zi])mg(.*?)\](.*?)\[\/[zi]mg\]/ism',$s,$matches,PREG_SET_ORDER);
if($c) {
require_once('include/photo/photo_driver.php');
foreach($matches as $mtch) {
logger('scale_external_image: ' . $mtch[2] . ' ' . $mtch[3]);
if(substr($mtch[1],0,1) == '=') {
$owidth = intval(substr($mtch[2],1));
if(intval($owidth) > 0 && intval($owidth) < 1024)
continue;
}
$hostname = str_replace('www.','',substr(z_root(),strpos(z_root(),'://')+3));
if(stristr($mtch[3],$hostname))
continue;
// $scale_replace, if passed, is an array of two elements. The
// first is the name of the full-size image. The second is the
// name of a remote, scaled-down version of the full size image.
// This allows Friendica to display the smaller remote image if
// one exists, while still linking to the full-size image
if($scale_replace)
$scaled = str_replace($scale_replace[0], $scale_replace[1], $mtch[3]);
else
$scaled = $mtch[3];
$i = z_fetch_url($scaled,true);
$cache = get_config('system','itemcache');
if (($cache != '') and is_dir($cache)) {
$cachefile = $cache."/".hash("md5", $scaled);
file_put_contents($cachefile, $i['body']);
}
// guess mimetype from headers or filename
$type = guess_image_type($mtch[3],$i['header']);
if(strpos($type,'image') === false)
continue;
if($i['success']) {
$ph = photo_factory($i['body'], $type);
if(! is_object($ph))
continue;
if($ph->is_valid()) {
$orig_width = $ph->getWidth();
$orig_height = $ph->getHeight();
if($orig_width > 1024 || $orig_height > 1024) {
$tag = (($match[1] == 'z') ? 'zmg' : 'img');
$ph->scaleImage(1024);
$new_width = $ph->getWidth();
$new_height = $ph->getHeight();
logger('scale_external_images: ' . $orig_width . '->' . $new_width . 'w ' . $orig_height . '->' . $new_height . 'h' . ' match: ' . $mtch[0], LOGGER_DEBUG);
$s = str_replace($mtch[0],'[' . $tag . '=' . $new_width . 'x' . $new_height. ']' . $scaled . '[/' . $tag . ']'
. "\n" . (($include_link)
? '[zrl=' . $mtch[2] . ']' . t('view full size') . '[/zrl]' . "\n"
: ''),$s);
logger('scale_external_images: new string: ' . $s, LOGGER_DEBUG);
}
}
}
}
}
// replace the special char encoding
$s = htmlspecialchars($s,ENT_COMPAT,'UTF-8');
return $s;
}
/**
* xml2array() will convert the given XML text to an array in the XML structure.
* Link: http://www.bin-co.com/php/scripts/xml2array/
* Portions significantly re-written by mike@macgirvin.com for Friendica (namespaces, lowercase tags, get_attribute default changed, more...)
* Arguments : $contents - The XML text
* $namespaces - true or false include namespace information in the returned array as array elements.
* $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.
* $priority - Can be 'tag' or 'attribute'. This will change the way the resulting array sturcture. For 'tag', the tags are given more importance.
* Return: The parsed XML in an array form. Use print_r() to see the resulting array structure.
* Examples: $array = xml2array(file_get_contents('feed.xml'));
* $array = xml2array(file_get_contents('feed.xml', true, 1, 'attribute'));
*/
function xml2array($contents, $namespaces = true, $get_attributes=1, $priority = 'attribute') {
if(!$contents) return array();
if(!function_exists('xml_parser_create')) {
logger('xml2array: parser function missing');
return array();
}
libxml_use_internal_errors(true);
libxml_clear_errors();
if($namespaces)
$parser = @xml_parser_create_ns("UTF-8",':');
else
$parser = @xml_parser_create();
if(! $parser) {
logger('xml2array: xml_parser_create: no resource');
return array();
}
xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, "UTF-8");
// http://minutillo.com/steve/weblog/2004/6/17/php-xml-and-character-encodings-a-tale-of-sadness-rage-and-data-loss
xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, 1);
@xml_parse_into_struct($parser, trim($contents), $xml_values);
@xml_parser_free($parser);
if(! $xml_values) {
logger('xml2array: libxml: parse error: ' . $contents, LOGGER_DATA);
foreach(libxml_get_errors() as $err)
logger('libxml: parse: ' . $err->code . " at " . $err->line . ":" . $err->column . " : " . $err->message, LOGGER_DATA);
libxml_clear_errors();
return;
}
//Initializations
$xml_array = array();
$parents = array();
$opened_tags = array();
$arr = array();
$current = &$xml_array; // Reference
// Go through the tags.
$repeated_tag_index = array(); // Multiple tags with same name will be turned into an array
foreach($xml_values as $data) {
unset($attributes,$value); // Remove existing values, or there will be trouble
// This command will extract these variables into the foreach scope
// tag(string), type(string), level(int), attributes(array).
extract($data); // We could use the array by itself, but this cooler.
$result = array();
$attributes_data = array();
if(isset($value)) {
if($priority == 'tag') $result = $value;
else $result['value'] = $value; // Put the value in a assoc array if we are in the 'Attribute' mode
}
//Set the attributes too.
if(isset($attributes) and $get_attributes) {
foreach($attributes as $attr => $val) {
if($priority == 'tag') $attributes_data[$attr] = $val;
else $result['@attributes'][$attr] = $val; // Set all the attributes in a array called 'attr'
}
}
// See tag status and do the needed.
if($namespaces && strpos($tag,':')) {
$namespc = substr($tag,0,strrpos($tag,':'));
$tag = strtolower(substr($tag,strlen($namespc)+1));
$result['@namespace'] = $namespc;
}
$tag = strtolower($tag);
if($type == "open") { // The starting of the tag '<tag>'
$parent[$level-1] = &$current;
if(!is_array($current) or (!in_array($tag, array_keys($current)))) { // Insert New tag
$current[$tag] = $result;
if($attributes_data) $current[$tag. '_attr'] = $attributes_data;
$repeated_tag_index[$tag.'_'.$level] = 1;
$current = &$current[$tag];
} else { // There was another element with the same tag name
if(isset($current[$tag][0])) { // If there is a 0th element it is already an array
$current[$tag][$repeated_tag_index[$tag.'_'.$level]] = $result;
$repeated_tag_index[$tag.'_'.$level]++;
} else { // This section will make the value an array if multiple tags with the same name appear together
$current[$tag] = array($current[$tag],$result); // This will combine the existing item and the new item together to make an array
$repeated_tag_index[$tag.'_'.$level] = 2;
if(isset($current[$tag.'_attr'])) { // The attribute of the last(0th) tag must be moved as well
$current[$tag]['0_attr'] = $current[$tag.'_attr'];
unset($current[$tag.'_attr']);
}
}
$last_item_index = $repeated_tag_index[$tag.'_'.$level]-1;
$current = &$current[$tag][$last_item_index];
}
} elseif($type == "complete") { // Tags that ends in 1 line '<tag />'
//See if the key is already taken.
if(!isset($current[$tag])) { //New Key
$current[$tag] = $result;
$repeated_tag_index[$tag.'_'.$level] = 1;
if($priority == 'tag' and $attributes_data) $current[$tag. '_attr'] = $attributes_data;
} else { // If taken, put all things inside a list(array)
if(isset($current[$tag][0]) and is_array($current[$tag])) { // If it is already an array...
// ...push the new element into that array.
$current[$tag][$repeated_tag_index[$tag.'_'.$level]] = $result;
if($priority == 'tag' and $get_attributes and $attributes_data) {
$current[$tag][$repeated_tag_index[$tag.'_'.$level] . '_attr'] = $attributes_data;
}
$repeated_tag_index[$tag.'_'.$level]++;
} else { // If it is not an array...
$current[$tag] = array($current[$tag],$result); //...Make it an array using using the existing value and the new value
$repeated_tag_index[$tag.'_'.$level] = 1;
if($priority == 'tag' and $get_attributes) {
if(isset($current[$tag.'_attr'])) { // The attribute of the last(0th) tag must be moved as well
$current[$tag]['0_attr'] = $current[$tag.'_attr'];
unset($current[$tag.'_attr']);
}
if($attributes_data) {
$current[$tag][$repeated_tag_index[$tag.'_'.$level] . '_attr'] = $attributes_data;
}
}
$repeated_tag_index[$tag.'_'.$level]++; // 0 and 1 indexes are already taken
}
}
} elseif($type == 'close') { // End of tag '</tag>'
$current = &$parent[$level-1];
}
}
return($xml_array);
}
function email_header_encode($in_str, $charset = 'UTF-8') {
$out_str = $in_str;
$need_to_convert = false;
for($x = 0; $x < strlen($in_str); $x ++) {
if((ord($in_str[$x]) == 0) || ((ord($in_str[$x]) > 128))) {
$need_to_convert = true;
}
}
if(! $need_to_convert)
return $in_str;
if ($out_str && $charset) {
// define start delimimter, end delimiter and spacer
$end = "?=";
$start = "=?" . $charset . "?B?";
$spacer = $end . "\r\n " . $start;
// determine length of encoded text within chunks
// and ensure length is even
$length = 75 - strlen($start) - strlen($end);
/*
[EDIT BY danbrown AT php DOT net: The following
is a bugfix provided by (gardan AT gmx DOT de)
on 31-MAR-2005 with the following note:
"This means: $length should not be even,
but divisible by 4. The reason is that in
base64-encoding 3 8-bit-chars are represented
by 4 6-bit-chars. These 4 chars must not be
split between two encoded words, according
to RFC-2047.
*/
$length = $length - ($length % 4);
// encode the string and split it into chunks
// with spacers after each chunk
$out_str = base64_encode($out_str);
$out_str = chunk_split($out_str, $length, $spacer);
// remove trailing spacer and
// add start and end delimiters
$spacer = preg_quote($spacer,'/');
$out_str = preg_replace("/" . $spacer . "$/", "", $out_str);
$out_str = $start . $out_str . $end;
}
return $out_str;
}
function email_send($addr, $subject, $headers, $item) {
//$headers .= 'MIME-Version: 1.0' . "\n";
//$headers .= 'Content-Type: text/html; charset=UTF-8' . "\n";
//$headers .= 'Content-Type: text/plain; charset=UTF-8' . "\n";
//$headers .= 'Content-Transfer-Encoding: 8bit' . "\n\n";
$part = uniqid("", true);
$html = prepare_body($item);
$headers .= "Mime-Version: 1.0\n";
$headers .= 'Content-Type: multipart/alternative; boundary="=_'.$part.'"'."\n\n";
$body = "\n--=_".$part."\n";
$body .= "Content-Transfer-Encoding: 8bit\n";
$body .= "Content-Type: text/plain; charset=utf-8; format=flowed\n\n";
$body .= html2plain($html)."\n";
$body .= "--=_".$part."\n";
$body .= "Content-Transfer-Encoding: 8bit\n";
$body .= "Content-Type: text/html; charset=utf-8\n\n";
$body .= '<html><head></head><body style="word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space; ">'.$html."</body></html>\n";
$body .= "--=_".$part."--";
//$message = '<html><body>' . $html . '</body></html>';
//$message = html2plain($html);
logger('notifier: email delivery to ' . $addr);
mail($addr, $subject, $body, $headers);
}
function discover_by_url($url,$arr = null) {
require_once('library/HTML5/Parser.php');
$x = scrape_feed($url);
if(! $x) {
if(! $arr)
return false;
$network = (($arr['network']) ? $arr['network'] : 'unknown');
$name = (($arr['name']) ? $arr['name'] : 'unknown');
$photo = (($arr['photo']) ? $arr['photo'] : '');
$addr = (($arr['addr']) ? $arr['addr'] : '');
$guid = $url;
}
$profile = $url;
logger('scrape_feed results: ' . print_r($x,true));
if($x['feed_atom'])
$guid = $x['feed_atom'];
if($x['feed_rss'])
$guid = $x['feed_rss'];
if(! $guid)
return false;
// try and discover stuff from the feeed
require_once('library/simplepie/simplepie.inc');
$feed = new SimplePie();
$level = 0;
$x = z_fetch_url($guid,false,$level,array('novalidate' => true));
if(! $x['success']) {
logger('probe_url: feed fetch failed for ' . $poll);
return false;
}
$xml = $x['body'];
logger('probe_url: fetch feed: ' . $guid . ' returns: ' . $xml, LOGGER_DATA);
logger('probe_url: scrape_feed: headers: ' . $x['header'], LOGGER_DATA);
// Don't try and parse an empty string
$feed->set_raw_data(($xml) ? $xml : '<?xml version="1.0" encoding="utf-8" ?><xml></xml>');
$feed->init();
if($feed->error())
logger('probe_url: scrape_feed: Error parsing XML: ' . $feed->error());
$name = unxmlify(trim($feed->get_title()));
$photo = $feed->get_image_url();
$author = $feed->get_author();
if($author) {
if(! $name)
$name = unxmlify(trim($author->get_name()));
if(! $name) {
$name = trim(unxmlify($author->get_email()));
if(strpos($name,'@') !== false)
$name = substr($name,0,strpos($name,'@'));
}
if(! $profile && $author->get_link())
$profile = trim(unxmlify($author->get_link()));
if(! $photo) {
$rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
if($rawtags) {
$elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo'))
$photo = $elems['link'][0]['attribs']['']['href'];
}
}
}
else {
$item = $feed->get_item(0);
if($item) {
$author = $item->get_author();
if($author) {
if(! $name) {
$name = trim(unxmlify($author->get_name()));
if(! $name)
$name = trim(unxmlify($author->get_email()));
if(strpos($name,'@') !== false)
$name = substr($name,0,strpos($name,'@'));
}
if(! $profile && $author->get_link())
$profile = trim(unxmlify($author->get_link()));
}
if(! $photo) {
$rawmedia = $item->get_item_tags('http://search.yahoo.com/mrss/','thumbnail');
if($rawmedia && $rawmedia[0]['attribs']['']['url'])
$photo = unxmlify($rawmedia[0]['attribs']['']['url']);
}
if(! $photo) {
$rawtags = $item->get_item_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
if($rawtags) {
$elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo'))
$photo = $elems['link'][0]['attribs']['']['href'];
}
}
}
}
if($poll === $profile)
$lnk = $feed->get_permalink();
if(isset($lnk) && strlen($lnk))
$profile = $lnk;
if(! $network) {
$network = 'rss';
}
if(! $name)
$name = notags($feed->get_description());
if(! $guid)
return false;
$r = q("select * from xchan where xchan_hash = '%s' limit 1",
dbesc($guid)
);
if($r)
return true;
if(! $photo)
$photo = z_root() . '/images/rss_icon.png';
$r = xchan_store_lowlevel(
[
'xchan_hash' => $guid,
'xchan_guid' => $guid,
'xchan_pubkey' => $pubkey,
'xchan_addr' => $addr,
'xchan_url' => $profile,
'xchan_name' => $name,
'xchan_name_date' => datetime_convert(),
'xchan_network' => $network
]
);
$photos = import_xchan_photo($photo,$guid);
$r = q("update xchan set xchan_photo_date = '%s', xchan_photo_l = '%s', xchan_photo_m = '%s', xchan_photo_s = '%s', xchan_photo_mimetype = '%s' where xchan_hash = '%s'",
dbesc(datetime_convert()),
dbesc($photos[0]),
dbesc($photos[1]),
dbesc($photos[2]),
dbesc($photos[3]),
dbesc($guid)
);
return true;
}
function discover_by_webbie($webbie) {
require_once('library/HTML5/Parser.php');
$result = array();
$network = null;
$diaspora = false;
$gnusoc = false;
$dfrn = false;
$has_salmon = false;
$salmon_key = false;
$atom_feed = false;
$diaspora_base = '';
$diaspora_guid = '';
$diaspora_key = '';
$webbie = strtolower($webbie);
$x = webfinger_rfc7033($webbie,true);
if($x && array_key_exists('links',$x) && $x['links']) {
foreach($x['links'] as $link) {
if(array_key_exists('rel',$link)) {
// If we discover zot - don't search further; grab the info and get out of
// here.
if($link['rel'] === PROTOCOL_ZOT) {
logger('discover_by_webbie: zot found for ' . $webbie, LOGGER_DEBUG);
if(array_key_exists('zot',$x) && $x['zot']['success']) {
$i = import_xchan($x['zot']);
return true;
}
else {
$z = z_fetch_url($link['href']);
if($z['success']) {
$j = json_decode($z['body'],true);
$i = import_xchan($j);
return true;
}
}
}
if($link['rel'] == NAMESPACE_DFRN) {
$dfrn = $link['href'];
}
if($link['rel'] == 'magic-public-key') {
if(substr($link['href'],0,5) === 'data:') {
$salmon_key = convert_salmon_key($link['href']);
}
}
if($link['rel'] == 'salmon') {
$has_salmon = true;
$salmon = $link['href'];
}
if($link['rel'] == 'http://schemas.google.com/g/2010#updates-from') {
$atom_feed = $link['href'];
}
}
}
}
logger('webfinger: ' . print_r($x,true), LOGGER_DATA, LOG_INFO);
$arr = array('address' => $webbie, 'success' => false, 'webfinger' => $x);
call_hooks('discover_channel_webfinger', $arr);
if($arr['success'])
return true;
$aliases = array();
// Now let's make some decisions on what we may need
// to obtain further info
$probe_atom = false;
$probe_old = false;
$probe_hcard = false;
$address = '';
$location = '';
$nickname = '';
$fullname = '';
$avatar = '';
$pubkey = '';
if(is_array($x)) {
if(array_key_exists('address',$x))
$address = $x['address'];
if(array_key_exists('location',$x))
$location = $x['location'];
if(array_key_exists('nickname',$x))
$nickname = $x['nickname'];
}
if(! $x)
$probe_old = true;
if((! $dfrn) && (! $has_salmon))
$probe_old = true;
if($probe_old) {
$y = old_webfinger($webbie);
if($y) {
logger('old_webfinger: ' . print_r($x,true));
foreach($y as $link) {
if($link['@attributes']['rel'] === NAMESPACE_DFRN)
$dfrn = unamp($link['@attributes']['href']);
if($link['@attributes']['rel'] === 'salmon')
$notify = unamp($link['@attributes']['href']);
if($link['@attributes']['rel'] === NAMESPACE_FEED)
$poll = unamp($link['@attributes']['href']);
if($link['@attributes']['rel'] === 'http://microformats.org/profile/hcard')
$hcard = unamp($link['@attributes']['href']);
if($link['@attributes']['rel'] === 'http://webfinger.net/rel/profile-page')
$profile = unamp($link['@attributes']['href']);
if($link['@attributes']['rel'] === 'http://portablecontacts.net/spec/1.0')
$poco = unamp($link['@attributes']['href']);
if($link['@attributes']['rel'] === 'http://joindiaspora.com/seed_location') {
$diaspora_base = unamp($link['@attributes']['href']);
$diaspora = true;
}
if($link['@attributes']['rel'] === 'http://joindiaspora.com/guid') {
$diaspora_guid = unamp($link['@attributes']['href']);
$diaspora = true;
}
if($link['@attributes']['rel'] === 'diaspora-public-key') {
$diaspora_key = base64_decode(unamp($link['@attributes']['href']));
if(strstr($diaspora_key,'RSA '))
$pubkey = rsatopem($diaspora_key);
else
$pubkey = $diaspora_key;
$diaspora = true;
}
if($link['@attributes']['rel'] == 'magic-public-key') {
if(substr($link['@attributes']['href'],0,5) === 'data:') {
$salmon_key = convert_salmon_key($link['@attributes']['href']);
}
}
if($link['@attributes']['rel'] == 'salmon') {
$has_salmon = true;
$salmon = $link['@attributes']['href'];
}
if($link['@attributes']['rel'] == 'http://schemas.google.com/g/2010#updates-from') {
$atom_feed = $link['@attributes']['href'];
}
if($link['@attributes']['rel'] === 'alias') {
$aliases[] = $link['@attributes']['href'];
}
if($link['@attributes']['rel'] === 'subject') {
$subject = $link['@attributes']['href'];
}
}
}
}
if($subject || $aliases) {
if(strpos($webbie,'@')) {
$rhs = substr($webbie,strpos($webbie,'@')+1);
}
else {
$m = parse_url($webbie);
if($m) {
$rhs = $m['host'] . (($m['port']) ? ':' . $m['port'] : '');
}
}
$v = array('subject' => $subject,'aliases' => $aliases);
$address = find_webfinger_address($v,$rhs);
$location = find_webfinger_location($v,$rhs);
if($address)
$nickname = substr($address,0,strpos($address,'@'));
}
if($salmon_key && $has_salmon && $atom_feed && (! $dfrn) && (! $diaspora)) {
$gnusoc = true;
$probe_atom = true;
}
if(! $pubkey)
$pubkey = $salmon_key;
if(($dfrn || $diaspora) && $hcard)
$probe_hcard = true;
if(! $fullname)
$fullname = $nickname;
if($probe_atom) {
$k = z_fetch_url($atom_feed);
if($k['success'])
$feed_meta = feed_meta($k['body']);
if($feed_meta) {
// stash any discovered pubsubhubbub hubs in case we need to follow them
// this will save an expensive lookup later
if($feed_meta['hubs'] && $address) {
set_xconfig($address,'system','push_hubs',$feed_meta['hubs']);
set_xconfig($address,'system','feed_url',$atom_feed);
}
if($feed_meta['author']['author_name']) {
$fullname = $feed_meta['author']['author_name'];
}
if(! $avatar) {
if($feed_meta['author']['author_photo'])
$avatar = $feed_meta['author']['author_photo'];
}
// for GNU-social over-ride any url aliases we may have picked up in webfinger
// The author.uri element in the feed is likely to be more accurate
if($gnusoc && $feed_meta['author']['author_uri'])
$location = $feed_meta['author']['author_uri'];
}
}
else {
if($probe_hcard) {
$vcard = scrape_vcard($hcard);
if($vcard) {
logger('vcard: ' . print_r($vcard,true), LOGGER_DATA);
if($vcard['fn'])
$fullname = $vcard['fn'];
if($vcard['photo'] && (strpos($vcard['photo'],'http') !== 0))
$vcard['photo'] = $diaspora_base . '/' . $vcard['photo'];
if(($vcard['public_key']) && (! $pubkey)) {
$diaspora_key = $vcard['public_key'];
if(strstr($diaspora_key,'RSA '))
$pubkey = rsatopem($diaspora_key);
else
$pubkey = $diaspora_key;
}
if(! $avatar)
$avatar = $vcard['photo'];
if($diaspora) {
if(($vcard['uid']) && (! $diaspora_guid))
$diaspora_guid = $vcard['uid'];
if(($vcard['url']) && (! $diaspora_base))
$diaspora_base = $vcard['url'];
}
}
}
}
if(($profile) && (! $location))
$location = $profile;
if($location) {
$m = parse_url($location);
$base = $m['scheme'] . '://' . $m['host'];
$host = $m['host'];
}
if($diaspora && $diaspora_base && $diaspora_guid) {
if($dfrn)
$network = 'friendica-over-diaspora';
else
$network = 'diaspora';
$base = trim($diaspora_base,'/');
$notify = $base . '/receive';
}
else {
if($gnusoc) {
$network = 'gnusoc';
$notify = $salmon;
}
}
logger('network: ' . $network);
logger('address: ' . $address);
logger('fullname: ' . $fullname);
logger('pubkey: ' . $pubkey);
logger('location: ' . $location);
// if we have everything we need, let's create the records
if($network && $address && $fullname && $pubkey && $location) {
$r = q("select * from xchan where xchan_hash = '%s' limit 1",
dbesc($address)
);
if($r) {
$r = q("update xchan set xchan_name = '%s', xchan_network = '%s', xchan_name_date = '%s' where xchan_hash = '%s'",
dbesc($fullname),
dbesc($network),
dbesc(datetime_convert()),
dbesc($address)
);
}
else {
$r = xchan_store_lowlevel(
[
'xchan_hash' => $address,
'xchan_guid' => (($diaspora_guid) ? $diaspora_guid : $location),
'xchan_pubkey' => $pubkey,
'xchan_addr' => $address,
'xchan_url' => $location,
'xchan_name' => $fullname,
'xchan_name_date' => datetime_convert(),
'xchan_network' => $network
]
);
$r = q("insert into xchan ( xchan_hash, xchan_guid, xchan_pubkey, xchan_addr, xchan_url, xchan_name, xchan_network, xchan_name_date ) values ('%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s') ",
dbesc($address),
dbesc(($diaspora_guid) ? $diaspora_guid : $location),
dbesc($pubkey),
dbesc($address),
dbesc($location),
dbesc($fullname),
dbesc($network),
dbescdate(datetime_convert())
);
}
$r = q("select * from hubloc where hubloc_hash = '%s' limit 1",
dbesc($address)
);
if(! $r) {
$r = q("insert into hubloc ( hubloc_guid, hubloc_hash, hubloc_addr, hubloc_network, hubloc_url, hubloc_host, hubloc_callback, hubloc_updated, hubloc_primary ) values ('%s','%s','%s','%s','%s','%s','%s','%s', 1)",
dbesc(($diaspora_guid) ? $diaspora_guid : $location),
dbesc($address),
dbesc($address),
dbesc($network),
dbesc($base),
dbesc($host),
dbesc($notify),
dbescdate(datetime_convert())
);
}
$photos = import_xchan_photo($avatar,$address);
$r = q("update xchan set xchan_photo_date = '%s', xchan_photo_l = '%s', xchan_photo_m = '%s', xchan_photo_s = '%s', xchan_photo_mimetype = '%s' where xchan_hash = '%s'",
dbescdate(datetime_convert()),
dbesc($photos[0]),
dbesc($photos[1]),
dbesc($photos[2]),
dbesc($photos[3]),
dbesc($address)
);
return true;
}
return false;
}
function webfinger_rfc7033($webbie,$zot = false) {
if(strpos($webbie,'@')) {
$lhs = substr($webbie,0,strpos($webbie,'@'));
$rhs = substr($webbie,strpos($webbie,'@')+1);
$resource = 'acct:' . $webbie;
}
else {
$m = parse_url($webbie);
if($m) {
if($m['scheme'] !== 'https')
return false;
$rhs = $m['host'] . (($m['port']) ? ':' . $m['port'] : '');
$resource = urlencode($webbie);
}
else
return false;
}
logger('fetching url from resource: ' . $rhs . ':' . $webbie);
$s = z_fetch_url('https://' . $rhs . '/.well-known/webfinger?f=&resource=' . $resource . (($zot) ? '&zot=1' : ''));
if($s['success']) {
$j = json_decode($s['body'],true);
// We could have a number of URL aliases and webbies
// make an executive decision about the most likely "best" of each
// by comparing against some examples from known networks we're likely to encounter.
// Otherwise we have to store every alias that we may ever encounter and
// validate every URL we ever find against every possible alias
// @fixme pump.io is going to be a real bugger since it doesn't return subject or aliases
// or provide lookup by url
$j['address'] = find_webfinger_address($j,$rhs);
$j['location'] = find_webfinger_location($j,$rhs);
if($j['address'])
$j['nickname'] = substr($j['address'],0,strpos($j['address'],'@'));
}
else
return false;
return($j);
}
function find_webfinger_address($j,$rhs) {
if(is_array($j) && ($j)) {
if(strpos($j['subject'],'acct:') !== false && strpos($j['subject'],'@' . $rhs))
return str_replace('acct:','',$j['subject']);
if($j['aliases']) {
foreach($j['aliases'] as $alias) {
if(strpos($alias,'acct:') !== false && strpos($alias,'@' . $rhs)) {
return str_replace('acct:','',$alias);
}
}
}
}
return '';
}
function find_webfinger_location($j,$rhs) {
if(is_array($j) && ($j)) {
if(strpos($j['subject'],'http') === 0) {
$x = match_webfinger_location($j['subject'],$rhs);
if($x)
return $x;
}
if($j['aliases']) {
foreach($j['aliases'] as $alias) {
if(strpos($alias,'http') === 0) {
$x = match_webfinger_location($alias,$rhs);
if($x)
return($x);
}
}
}
}
return '';
}
function match_webfinger_location($s,$h) {
// GNU-social and the older StatusNet - the $host/user/123 form doesn't work
if(preg_match('|' . $h . '/index.php/user/([0-9]*?)$|',$s))
return $s;
// Redmatrix / hubzilla
if(preg_match('|' . $h . '/channel/|',$s))
return $s;
// Friendica
if(preg_match('|' . $h . '/profile/|',$s))
return $s;
$arr = array('test' => $s, 'host' => $h, 'success' => false);
call_hooks('match_webfinger_location',$arr);
if($arr['success'])
return $s;
return '';
}
function old_webfinger($webbie) {
$host = '';
if(strstr($webbie,'@'))
$host = substr($webbie,strpos($webbie,'@') + 1);
if(strlen($host)) {
$tpl = fetch_lrdd_template($host);
logger('old_webfinger: lrdd template: ' . $tpl,LOGGER_DATA);
if(strlen($tpl)) {
$pxrd = str_replace('{uri}', urlencode('acct:' . $webbie), $tpl);
logger('old_webfinger: pxrd: ' . $pxrd,LOGGER_DATA);
$links = fetch_xrd_links($pxrd);
if(! count($links)) {
// try with double slashes
$pxrd = str_replace('{uri}', urlencode('acct://' . $webbie), $tpl);
logger('old_webfinger: pxrd: ' . $pxrd,LOGGER_DATA);
$links = fetch_xrd_links($pxrd);
}
return $links;
}
}
return array();
}
function fetch_lrdd_template($host) {
$tpl = '';
$url1 = 'https://' . $host . '/.well-known/host-meta' ;
$url2 = 'http://' . $host . '/.well-known/host-meta' ;
$links = fetch_xrd_links($url1);
logger('fetch_lrdd_template from: ' . $url1, LOGGER_DEBUG);
logger('template (https): ' . print_r($links,true),LOGGER_DEBUG);
if(! count($links)) {
logger('fetch_lrdd_template from: ' . $url2);
$links = fetch_xrd_links($url2);
logger('template (http): ' . print_r($links,true),LOGGER_DEBUG);
}
if(count($links)) {
foreach($links as $link)
if($link['@attributes']['rel'] && $link['@attributes']['rel'] === 'lrdd' && (!$link['@attributes']['type'] || $link['@attributes']['type'] === 'application/xrd+xml'))
$tpl = $link['@attributes']['template'];
}
if(! strpos($tpl,'{uri}'))
$tpl = '';
return $tpl;
}
function fetch_xrd_links($url) {
logger('fetch_xrd_links: ' . $url, LOGGER_DEBUG);
$redirects = 0;
$x = z_fetch_url($url,false,$redirects,array('timeout' => 20));
if(! $x['success'])
return array();
$xml = $x['body'];
logger('fetch_xrd_links: ' . $xml, LOGGER_DATA);
if ((! $xml) || (! stristr($xml,'<xrd')))
return array();
// fix diaspora's bad xml
$xml = str_replace(array('href="','"/>'),array('href="','"/>'),$xml);
$h = parse_xml_string($xml);
if(! $h)
return array();
$arr = convert_xml_element_to_array($h);
$links = array();
if(isset($arr['xrd']['link'])) {
$link = $arr['xrd']['link'];
if(! isset($link[0]))
$links = array($link);
else
$links = $link;
}
if(isset($arr['xrd']['alias'])) {
$alias = $arr['xrd']['alias'];
if(! isset($alias[0]))
$aliases = array($alias);
else
$aliases = $alias;
if(is_array($aliases) && count($aliases)) {
foreach($aliases as $alias) {
$links[]['@attributes'] = array('rel' => 'alias' , 'href' => $alias);
}
}
}
if(isset($arr['xrd']['subject'])) {
$links[]['@attributes'] = array('rel' => 'subject' , 'href' => $arr['xrd']['subject']);
}
logger('fetch_xrd_links: ' . print_r($links,true), LOGGER_DATA);
return $links;
}
function scrape_vcard($url) {
$ret = array();
logger('scrape_vcard: url=' . $url);
$x = z_fetch_url($url);
if(! $x['success'])
return $ret;
$s = $x['body'];
if(! $s)
return $ret;
$headers = $x['header'];
$lines = explode("\n",$headers);
if(count($lines)) {
foreach($lines as $line) {
// don't try and run feeds through the html5 parser
if(stristr($line,'content-type:') && ((stristr($line,'application/atom+xml')) || (stristr($line,'application/rss+xml'))))
return ret;
}
}
try {
$dom = HTML5_Parser::parse($s);
} catch (DOMException $e) {
logger('scrape_vcard: parse error: ' . $e);
}
if(! $dom)
return $ret;
// Pull out hCard profile elements
$largest_photo = 0;
$items = $dom->getElementsByTagName('*');
foreach($items as $item) {
if(attribute_contains($item->getAttribute('class'), 'vcard')) {
$level2 = $item->getElementsByTagName('*');
foreach($level2 as $x) {
if(attribute_contains($x->getAttribute('id'),'pod_location'))
$ret['pod_location'] = $x->textContent;
if(attribute_contains($x->getAttribute('class'),'fn'))
$ret['fn'] = $x->textContent;
if(attribute_contains($x->getAttribute('class'),'uid'))
$ret['uid'] = $x->textContent;
if(attribute_contains($x->getAttribute('class'),'nickname'))
$ret['nick'] = $x->textContent;
if(attribute_contains($x->getAttribute('class'),'searchable'))
$ret['searchable'] = $x->textContent;
if(attribute_contains($x->getAttribute('class'),'key'))
$ret['public_key'] = $x->textContent;
if(attribute_contains($x->getAttribute('class'),'given_name'))
$ret['given_name'] = $x->textContent;
if(attribute_contains($x->getAttribute('class'),'family_name'))
$ret['family_name'] = $x->textContent;
if(attribute_contains($x->getAttribute('class'),'url'))
$ret['url'] = $x->textContent;
if((attribute_contains($x->getAttribute('class'),'photo'))
|| (attribute_contains($x->getAttribute('class'),'avatar'))) {
$size = intval($x->getAttribute('width'));
if(($size > $largest_photo) || (! $largest_photo)) {
$ret['photo'] = $x->getAttribute('src');
$largest_photo = $size;
}
}
}
}
}
return $ret;
}
function scrape_feed($url) {
$ret = array();
$level = 0;
$x = z_fetch_url($url,false,$level,array('novalidate' => true));
if(! $x['success'])
return $ret;
$headers = $x['header'];
$code = $x['return_code'];
$s = $x['body'];
logger('scrape_feed: returns: ' . $code . ' headers=' . $headers, LOGGER_DEBUG);
if(! $s) {
logger('scrape_feed: no data returned for ' . $url);
return $ret;
}
$lines = explode("\n",$headers);
if(count($lines)) {
foreach($lines as $line) {
if(stristr($line,'content-type:')) {
if(stristr($line,'application/atom+xml') || stristr($s,'<feed')) {
$ret['feed_atom'] = $url;
return $ret;
}
if(stristr($line,'application/rss+xml') || stristr($s,'<rss')) {
$ret['feed_rss'] = $url;
return $ret;
}
}
}
// perhaps an RSS version 1 feed with a generic or incorrect content-type?
if(stristr($s,'</item>')) {
$ret['feed_rss'] = $url;
return $ret;
}
}
try {
$dom = HTML5_Parser::parse($s);
} catch (DOMException $e) {
logger('scrape_feed: parse error: ' . $e);
}
if(! $dom) {
logger('scrape_feed: failed to parse.');
return $ret;
}
$head = $dom->getElementsByTagName('base');
if($head) {
foreach($head as $head0) {
$basename = $head0->getAttribute('href');
break;
}
}
if(! $basename)
$basename = implode('/', array_slice(explode('/',$url),0,3)) . '/';
$items = $dom->getElementsByTagName('link');
// get Atom/RSS link elements, take the first one of either.
if($items) {
foreach($items as $item) {
$x = $item->getAttribute('rel');
if(($x === 'alternate') && ($item->getAttribute('type') === 'application/atom+xml')) {
if(! x($ret,'feed_atom'))
$ret['feed_atom'] = $item->getAttribute('href');
}
if(($x === 'alternate') && ($item->getAttribute('type') === 'application/rss+xml')) {
if(! x($ret,'feed_rss'))
$ret['feed_rss'] = $item->getAttribute('href');
}
}
}
// Drupal and perhaps others only provide relative URL's. Turn them into absolute.
if(x($ret,'feed_atom') && (! strstr($ret['feed_atom'],'://')))
$ret['feed_atom'] = $basename . $ret['feed_atom'];
if(x($ret,'feed_rss') && (! strstr($ret['feed_rss'],'://')))
$ret['feed_rss'] = $basename . $ret['feed_rss'];
return $ret;
}
function service_plink($contact, $guid) {
$plink = '';
$m = parse_url($contact['xchan_url']);
if($m) {
$url = $m['scheme'] . '://' . $m['host'] . (($m['port']) ? ':' . $m['port'] : '');
}
else
$url = 'https://' . substr($contact['xchan_addr'],strpos($contact['xchan_addr'],'@')+1);
$handle = substr($contact['xchan_addr'], 0, strpos($contact['xchan_addr'],'@'));
if($contact['xchan_network'] === 'diaspora')
$plink = $url . '/posts/' . $guid;
if($contact['xchan_network'] === 'friendica-over-diaspora')
$plink = $url . '/display/' . $handle . '/' . $guid;
if($contact['xchan_network'] === 'zot')
$plink = $url . '/channel/' . $handle . '?f=&mid=' . $guid;
return $plink;
}
function format_and_send_email($sender,$xchan,$item) {
$title = $item['title'];
$body = $item['body'];
$textversion = strip_tags(html_entity_decode(bbcode(str_replace(array("\\r", "\\n"), array( "", "\n"), $body)),ENT_QUOTES,'UTF-8'));
$htmlversion = bbcode(str_replace(array("\\r","\\n"), array("","<br />\n"),$body));
$banner = t('$Projectname Notification');
$product = t('$projectname'); // PLATFORM_NAME;
$siteurl = z_root();
$thanks = t('Thank You,');
$sitename = get_config('system','sitename');
$site_admin = sprintf( t('%s Administrator'), $sitename);
// load the template for private message notifications
$tpl = get_markup_template('email_notify_html.tpl');
$email_html_body = replace_macros($tpl,array(
'$banner' => $banner,
'$notify_icon' => Zotlabs\Lib\System::get_notify_icon(),
'$product' => $product,
'$preamble' => '',
'$sitename' => $sitename,
'$siteurl' => $siteurl,
'$source_name' => $sender['xchan_name'],
'$source_link' => $sender['xchan_url'],
'$source_photo' => $sender['xchan_photo_m'],
'$username' => $xchan['xchan_name'],
'$hsitelink' => $datarray['hsitelink'],
'$hitemlink' => $datarray['hitemlink'],
'$thanks' => $thanks,
'$site_admin' => $site_admin,
'$title' => $title,
'$htmlversion' => $htmlversion,
));
// load the template for private message notifications
$tpl = get_markup_template('email_notify_text.tpl');
$email_text_body = replace_macros($tpl, array(
'$banner' => $banner,
'$product' => $product,
'$preamble' => '',
'$sitename' => $sitename,
'$siteurl' => $siteurl,
'$source_name' => $sender['xchan_name'],
'$source_link' => $sender['xchan_url'],
'$source_photo' => $sender['xchan_photo_m'],
'$username' => $xchan['xchan_name'],
'$hsitelink' => $datarray['hsitelink'],
'$hitemlink' => $datarray['hitemlink'],
'$thanks' => $thanks,
'$site_admin' => $site_admin,
'$title' => $title,
'$textversion' => $textversion
));
$sender_name = t('Administrator');
$hostname = App::get_hostname();
if(strpos($hostname,':'))
$hostname = substr($hostname,0,strpos($hostname,':'));
$sender_email = get_config('system','reply_address');
if(! $sender_email)
$sender_email = 'noreply' . '@' . $hostname;
// use the EmailNotification library to send the message
Zotlabs\Lib\Enotify::send(array(
'fromName' => $product,
'fromEmail' => $sender_email,
'replyTo' => $sender_email,
'toEmail' => str_replace('mailto:','',$xchan['xchan_addr']),
'messageSubject' => (($title) ? $title : t('No Subject')),
'htmlVersion' => $email_html_body,
'textVersion' => $email_text_body,
'additionalMailHeader' => '',
));
}
function do_delivery($deliveries) {
if(! (is_array($deliveries) && count($deliveries)))
return;
$interval = ((get_config('system','delivery_interval') !== false)
? intval(get_config('system','delivery_interval')) : 2 );
$deliveries_per_process = intval(get_config('system','delivery_batch_count'));
if($deliveries_per_process <= 0)
$deliveries_per_process = 1;
$deliver = array();
foreach($deliveries as $d) {
if(! $d)
continue;
$deliver[] = $d;
if(count($deliver) >= $deliveries_per_process) {
Zotlabs\Daemon\Master::Summon(array('Deliver',$deliver));
$deliver = array();
if($interval)
@time_sleep_until(microtime(true) + (float) $interval);
}
}
// catch any stragglers
if($deliver)
Zotlabs\Daemon\Master::Summon(array('Deliver',$deliver));
}
function get_site_info() {
$register_policy = Array('REGISTER_CLOSED', 'REGISTER_APPROVE', 'REGISTER_OPEN');
$directory_mode = Array('DIRECTORY_MODE_NORMAL', 'DIRECTORY_MODE_PRIMARY', 'DIRECTORY_MODE_SECONDARY', 256 => 'DIRECTORY_MODE_STANDALONE');
$sql_extra = '';
$r = q("select * from channel left join account on account_id = channel_account_id where ( account_roles & 4096 )>0 and account_default_channel = channel_id");
if($r) {
$admin = array();
foreach($r as $rr) {
if($rr['channel_pageflags'] & PAGE_HUBADMIN)
$admin[] = array( 'name' => $rr['channel_name'], 'address' => channel_reddress($rr), 'channel' => z_root() . '/channel/' . $rr['channel_address']);
}
if(! $admin) {
foreach($r as $rr) {
$admin[] = array( 'name' => $rr['channel_name'], 'address' => channel_reddress($rr), 'channel' => z_root() . '/channel/' . $rr['channel_address']);
}
}
}
else {
$admin = false;
}
$def_service_class = get_config('system','default_service_class');
if($def_service_class)
$service_class = get_config('service_class',$def_service_class);
else
$service_class = false;
$visible_plugins = visible_plugin_list();
if(@is_dir('.git') && function_exists('shell_exec'))
$commit = trim(@shell_exec('git log -1 --format="%h"'));
if(! isset($commit) || strlen($commit) > 16)
$commit = '';
$site_info = get_config('system','info');
$site_name = get_config('system','sitename');
if(! get_config('system','hidden_version_siteinfo')) {
$version = Zotlabs\Lib\System::get_project_version();
$tag = Zotlabs\Lib\System::get_std_version();
if(@is_dir('.git') && function_exists('shell_exec')) {
$commit = trim( @shell_exec('git log -1 --format="%h"'));
}
if(! isset($commit) || strlen($commit) > 16)
$commit = '';
}
else {
$version = $commit = '';
}
//Statistics
$channels_total_stat = intval(get_config('system','channels_total_stat'));
$channels_active_halfyear_stat = intval(get_config('system','channels_active_halfyear_stat'));
$channels_active_monthly_stat = intval(get_config('system','channels_active_monthly_stat'));
$local_posts_stat = intval(get_config('system','local_posts_stat'));
$hide_in_statistics = intval(get_config('system','hide_in_statistics'));
$site_expire = intval(get_config('system', 'default_expire_days'));
load_config('feature_lock');
$locked_features = array();
if(is_array(App::$config['feature_lock']) && count(App::$config['feature_lock'])) {
foreach(App::$config['feature_lock'] as $k => $v) {
if($k === 'config_loaded')
continue;
$locked_features[$k] = intval($v);
}
}
$data = Array(
'version' => $version,
'version_tag' => $tag,
'server_role' => Zotlabs\Lib\System::get_server_role(),
'commit' => $commit,
'url' => z_root(),
'plugins' => $visible_plugins,
'register_policy' => $register_policy[get_config('system','register_policy')],
'invitation_only' => intval(get_config('system','invitation_only')),
'directory_mode' => $directory_mode[get_config('system','directory_mode')],
'language' => get_config('system','language'),
'rss_connections' => intval(get_config('system','feed_contacts')),
'expiration' => $site_expire,
'default_service_restrictions' => $service_class,
'locked_features' => $locked_features,
'admin' => $admin,
'site_name' => (($site_name) ? $site_name : ''),
'platform' => Zotlabs\Lib\System::get_platform_name(),
'dbdriver' => DBA::$dba->getdriver(),
'lastpoll' => get_config('system','lastpoll'),
'info' => (($site_info) ? $site_info : ''),
'channels_total' => $channels_total_stat,
'channels_active_halfyear' => $channels_active_halfyear_stat,
'channels_active_monthly' => $channels_active_monthly_stat,
'local_posts' => $local_posts_stat,
'hide_in_statistics' => $hide_in_statistics
);
return $data;
}
function check_siteallowed($url) {
$retvalue = true;
$arr = array('url' => $url);
call_hooks('check_siteallowed',$arr);
if(array_key_exists('allowed',$arr))
return $arr['allowed'];
$bl1 = get_config('system','whitelisted_sites');
if(is_array($bl1) && $bl1) {
foreach($bl1 as $bl) {
if($bl1 === '*')
$retvalue = true;
if($bl && strpos($url,$bl) !== false)
return true;
}
}
$bl1 = get_config('system','blacklisted_sites');
if(is_array($bl1) && $bl1) {
foreach($bl1 as $bl) {
if($bl1 === '*')
$retvalue = false;
if($bl && strpos($url,$bl) !== false) {
return false;
}
}
}
return $retvalue;
}
function check_channelallowed($hash) {
$retvalue = true;
$arr = array('hash' => $hash);
call_hooks('check_channelallowed',$arr);
if(array_key_exists('allowed',$arr))
return $arr['allowed'];
$bl1 = get_config('system','whitelisted_channels');
if(is_array($bl1) && $bl1) {
foreach($bl1 as $bl) {
if($bl1 === '*')
$retvalue = true;
if($bl && strpos($hash,$bl) !== false)
return true;
}
}
$bl1 = get_config('system','blacklisted_channels');
if(is_array($bl1) && $bl1) {
foreach($bl1 as $bl) {
if($bl1 === '*')
$retvalue = false;
if($bl && strpos($hash,$bl) !== false) {
return false;
}
}
}
return $retvalue;
}
function deliverable_singleton($channel_id,$xchan) {
$r = q("select abook_instance from abook where abook_channel = %d and abook_xchan = '%s' limit 1",
intval($channel_id),
dbesc($xchan['xchan_hash'])
);
if($r) {
if(! $r[0]['abook_instance'])
return true;
if(strpos($r[0]['abook_instance'],z_root()) !== false)
return true;
}
return false;
}
function get_repository_version($branch = 'master') {
$path = "https://raw.githubusercontent.com/redmatrix/hubzilla/$branch/boot.php";
$x = z_fetch_url($path);
if($x['success']) {
$y = preg_match('/define(.*?)STD_VERSION(.*?)([0-9.].*)\'/',$x['body'],$matches);
if($y)
return $matches[3];
}
return '?.?';
}
function network_to_name($s) {
$nets = array(
NETWORK_DFRN => t('Friendica'),
NETWORK_FRND => t('Friendica'),
NETWORK_OSTATUS => t('OStatus'),
NETWORK_GNUSOCIAL => t('GNU-Social'),
NETWORK_FEED => t('RSS/Atom'),
NETWORK_MAIL => t('Email'),
NETWORK_DIASPORA => t('Diaspora'),
NETWORK_FACEBOOK => t('Facebook'),
NETWORK_ZOT => t('Zot'),
NETWORK_LINKEDIN => t('LinkedIn'),
NETWORK_XMPP => t('XMPP/IM'),
NETWORK_MYSPACE => t('MySpace'),
);
call_hooks('network_to_name', $nets);
$search = array_keys($nets);
$replace = array_values($nets);
return str_replace($search,$replace,$s);
}
function z_mail($params) {
/**
* @brief Send a text email message
*
* @param array $params an assoziative array with:
* * \e string \b fromName name of the sender
* * \e string \b fromEmail email of the sender
* * \e string \b replyTo replyTo address to direct responses
* * \e string \b toEmail destination email address
* * \e string \b messageSubject subject of the message
* * \e string \b htmlVersion html version of the message
* * \e string \b textVersion text only version of the message
* * \e string \b additionalMailHeader additions to the smtp mail header
*/
if(! $params['fromEmail']) {
$params['fromEmail'] = get_config('system','from_email');
if(! $params['fromEmail'])
$params['fromEmail'] = 'Administrator' . '@' . App::get_hostname();
}
if(! $params['fromName']) {
$params['fromName'] = get_config('system','from_email_name');
if(! $params['fromName'])
$params['fromName'] = Zotlabs\Lib\System::get_site_name();
}
if(! $params['replyTo']) {
$params['replyTo'] = get_config('system','reply_address');
if(! $params['replyTo'])
$params['replyTo'] = 'noreply' . '@' . App::get_hostname();
}
$params['sent'] = false;
$params['result'] = false;
call_hooks('email_send', $params);
if($params['sent']) {
logger('notification: z_mail returns ' . (($params['result']) ? 'success' : 'failure'), LOGGER_DEBUG);
return $params['result'];
}
$fromName = email_header_encode(html_entity_decode($params['fromName'],ENT_QUOTES,'UTF-8'),'UTF-8');
$messageSubject = email_header_encode(html_entity_decode($params['messageSubject'],ENT_QUOTES,'UTF-8'),'UTF-8');
$messageHeader =
$params['additionalMailHeader'] .
"From: $fromName <{$params['fromEmail']}>\n" .
"Reply-To: $fromName <{$params['replyTo']}>";
// send the message
$res = mail(
$params['toEmail'], // send to address
$messageSubject, // subject
$params['textVersion'],
$messageHeader // message headers
);
logger('notification: z_mail returns ' . (($res) ? 'success' : 'failure'), LOGGER_DEBUG);
return $res;
}
// discover the best API path available for redmatrix/hubzilla servers
function probe_api_path($host) {
$schemes = ['https', 'http' ];
$paths = ['/api/z/1.0/version', '/api/red/version' ];
foreach($schemes as $scheme) {
foreach($paths as $path) {
$curpath = $scheme . '://' . $host . $path;
$x = z_fetch_url($curpath);
if($x['success'] && ! strlen($x['body'],'not implemented'))
return str_replace('version','',$curpath);
}
}
return '';
}