use Friendica\Core\Config;
use Friendica\Network\Probe;
use Friendica\Object\Image;
+use Friendica\Util\Network;
use Friendica\Util\XML;
-/**
- * @brief Curl wrapper
- *
- * If binary flag is true, return binary results.
- * Set the cookiejar argument to a string (e.g. "/tmp/friendica-cookies.txt")
- * to preserve cookies from one request to the next.
- *
- * @param string $url URL to fetch
- * @param boolean $binary default false
- * TRUE if asked to return binary results (file download)
- * @param integer $redirects The recursion counter for internal use - default 0
- * @param integer $timeout Timeout in seconds, default system config value or 60 seconds
- * @param string $accept_content supply Accept: header with 'accept_content' as the value
- * @param string $cookiejar Path to cookie jar file
- *
- * @return string The fetched content
- */
function fetch_url($url, $binary = false, &$redirects = 0, $timeout = 0, $accept_content = null, $cookiejar = 0)
{
- $ret = z_fetch_url(
- $url,
- $binary,
- $redirects,
- ['timeout'=>$timeout,
- 'accept_content'=>$accept_content,
- 'cookiejar'=>$cookiejar
- ]
- );
-
- return($ret['body']);
+ return Network::fetchURL($url, $binary, $redirects, $timeout, $accept_content, $cookiejar);
}
-/**
- * @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 The recursion counter for internal use - default 0
- * @param array $opts (optional parameters) assoziative array with:
- * 'accept_content' => supply Accept: header with 'accept_content' as the value
- * 'timeout' => int Timeout in 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
- * 'nobody' => only return the header
- * 'cookiejar' => path to cookie jar file
- *
- * @return array an assoziative array with:
- * int 'return_code' => HTTP return code or 0 if timeout or failure
- * boolean 'success' => boolean true (if HTTP 2xx result) or false
- * string 'redirect_url' => in case of redirect, content was finally retrieved from this URL
- * string 'header' => HTTP headers
- * string 'body' => fetched content
- */
function z_fetch_url($url, $binary = false, &$redirects = 0, $opts = [])
{
- $ret = ['return_code' => 0, 'success' => false, 'header' => '', 'info' => '', 'body' => ''];
-
- $stamp1 = microtime(true);
-
- $a = get_app();
-
- if (blocked_url($url)) {
- logger('z_fetch_url: domain of ' . $url . ' is blocked', LOGGER_DATA);
- return $ret;
- }
-
- $ch = @curl_init($url);
-
- if (($redirects > 8) || (!$ch)) {
- return $ret;
- }
-
- @curl_setopt($ch, CURLOPT_HEADER, true);
-
- if (x($opts, "cookiejar")) {
- curl_setopt($ch, CURLOPT_COOKIEJAR, $opts["cookiejar"]);
- curl_setopt($ch, CURLOPT_COOKIEFILE, $opts["cookiejar"]);
- }
-
- // These settings aren't needed. We're following the location already.
- // @curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
- // @curl_setopt($ch, CURLOPT_MAXREDIRS, 5);
-
- if (x($opts, 'accept_content')) {
- curl_setopt(
- $ch,
- CURLOPT_HTTPHEADER,
- ['Accept: ' . $opts['accept_content']]
- );
- }
-
- @curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
- @curl_setopt($ch, CURLOPT_USERAGENT, $a->get_useragent());
-
- $range = intval(Config::get('system', 'curl_range_bytes', 0));
-
- if ($range > 0) {
- @curl_setopt($ch, CURLOPT_RANGE, '0-' . $range);
- }
-
- // Without this setting it seems as if some webservers send compressed content
- // This seems to confuse curl so that it shows this uncompressed.
- /// @todo We could possibly set this value to "gzip" or something similar
- curl_setopt($ch, CURLOPT_ENCODING, '');
-
- 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, 'timeout')) {
- @curl_setopt($ch, CURLOPT_TIMEOUT, $opts['timeout']);
- } else {
- $curl_time = Config::get('system', 'curl_timeout', 60);
- @curl_setopt($ch, CURLOPT_TIMEOUT, intval($curl_time));
- }
-
- // by default we will allow self-signed certs
- // but you can override this
-
- $check_cert = Config::get('system', 'verifyssl');
- @curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, (($check_cert) ? true : false));
-
- if ($check_cert) {
- @curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
- }
-
- $proxy = Config::get('system', 'proxy');
-
- if (strlen($proxy)) {
- @curl_setopt($ch, CURLOPT_HTTPPROXYTUNNEL, 1);
- @curl_setopt($ch, CURLOPT_PROXY, $proxy);
- $proxyuser = @Config::get('system', 'proxyuser');
-
- if (strlen($proxyuser)) {
- @curl_setopt($ch, CURLOPT_PROXYUSERPWD, $proxyuser);
- }
- }
-
- if (Config::get('system', 'ipv4_resolve', false)) {
- curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
- }
-
- if ($binary) {
- @curl_setopt($ch, CURLOPT_BINARYTRANSFER, 1);
- }
-
- $a->set_curl_code(0);
-
- // don't let curl abort the entire application
- // if it throws any errors.
-
- $s = @curl_exec($ch);
- $curl_info = @curl_getinfo($ch);
-
- // Special treatment for HTTP Code 416
- // See https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/416
- if (($curl_info['http_code'] == 416) && ($range > 0)) {
- @curl_setopt($ch, CURLOPT_RANGE, '');
- $s = @curl_exec($ch);
- $curl_info = @curl_getinfo($ch);
- }
-
- if (curl_errno($ch) !== CURLE_OK) {
- logger('fetch_url error fetching ' . $url . ': ' . curl_error($ch), LOGGER_NORMAL);
- }
-
- $ret['errno'] = curl_errno($ch);
-
- $base = $s;
- $ret['info'] = $curl_info;
-
- $http_code = $curl_info['http_code'];
-
- logger('fetch_url ' . $url . ': ' . $http_code . " " . $s, LOGGER_DATA);
- $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));
- }
-
- $a->set_curl_code($http_code);
- $a->set_curl_content_type($curl_info['content_type']);
- $a->set_curl_headers($header);
-
- if ($http_code == 301 || $http_code == 302 || $http_code == 303 || $http_code == 307) {
- $new_location_info = @parse_url($curl_info['redirect_url']);
- $old_location_info = @parse_url($curl_info['url']);
-
- $newurl = $curl_info['redirect_url'];
-
- if (($new_location_info['path'] == '') && ( $new_location_info['host'] != '')) {
- $newurl = $new_location_info['scheme'] . '://' . $new_location_info['host'] . $old_location_info['path'];
- }
-
- $matches = [];
-
- if (preg_match('/(Location:|URI:)(.*?)\n/i', $header, $matches)) {
- $newurl = trim(array_pop($matches));
- }
- if (strpos($newurl, '/') === 0) {
- $newurl = $old_location_info["scheme"]."://".$old_location_info["host"].$newurl;
- }
-
- if (filter_var($newurl, FILTER_VALIDATE_URL)) {
- $redirects++;
- @curl_close($ch);
- return z_fetch_url($newurl, $binary, $redirects, $opts);
- }
- }
-
- $a->set_curl_code($http_code);
- $a->set_curl_content_type($curl_info['content_type']);
-
- $rc = intval($http_code);
- $ret['return_code'] = $rc;
- $ret['success'] = (($rc >= 200 && $rc <= 299) ? true : false);
- $ret['redirect_url'] = $url;
-
- if (!$ret['success']) {
- $ret['error'] = curl_error($ch);
- $ret['debug'] = $curl_info;
- logger('z_fetch_url: error: '.$url.': '.$ret['return_code'].' - '.$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);
-
- $a->save_timestamp($stamp1, 'network');
-
- return($ret);
+ return Network::zFetchURL($url, $binary, $redirects, $opts);
}
-/**
- * @brief Send POST request to $url
- *
- * @param string $url URL to post
- * @param mixed $params array of POST variables
- * @param string $headers HTTP headers
- * @param integer $redirects Recursion counter for internal use - default = 0
- * @param integer $timeout The timeout in seconds, default system config value or 60 seconds
- *
- * @return string The content
- */
function post_url($url, $params, $headers = null, &$redirects = 0, $timeout = 0)
{
- $stamp1 = microtime(true);
-
- if (blocked_url($url)) {
- logger('post_url: domain of ' . $url . ' is blocked', LOGGER_DATA);
- return false;
- }
-
- $a = get_app();
- $ch = curl_init($url);
-
- if (($redirects > 8) || (!$ch)) {
- return false;
- }
-
- logger('post_url: start ' . $url, LOGGER_DATA);
-
- curl_setopt($ch, CURLOPT_HEADER, true);
- curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
- curl_setopt($ch, CURLOPT_POST, 1);
- curl_setopt($ch, CURLOPT_POSTFIELDS, $params);
- curl_setopt($ch, CURLOPT_USERAGENT, $a->get_useragent());
-
- if (Config::get('system', 'ipv4_resolve', false)) {
- curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
- }
-
- if (intval($timeout)) {
- curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
- } else {
- $curl_time = Config::get('system', 'curl_timeout', 60);
- curl_setopt($ch, CURLOPT_TIMEOUT, intval($curl_time));
- }
-
- if (defined('LIGHTTPD')) {
- if (!is_array($headers)) {
- $headers = ['Expect:'];
- } else {
- if (!in_array('Expect:', $headers)) {
- array_push($headers, 'Expect:');
- }
- }
- }
-
- if ($headers) {
- curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
- }
-
- $check_cert = Config::get('system', 'verifyssl');
- curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, (($check_cert) ? true : false));
-
- if ($check_cert) {
- @curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
- }
-
- $proxy = Config::get('system', 'proxy');
-
- if (strlen($proxy)) {
- curl_setopt($ch, CURLOPT_HTTPPROXYTUNNEL, 1);
- curl_setopt($ch, CURLOPT_PROXY, $proxy);
- $proxyuser = Config::get('system', 'proxyuser');
- if (strlen($proxyuser)) {
- curl_setopt($ch, CURLOPT_PROXYUSERPWD, $proxyuser);
- }
- }
-
- $a->set_curl_code(0);
-
- // 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('post_url: result ' . $http_code . ' - ' . $url, LOGGER_DATA);
-
- $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) {
- $matches = [];
- preg_match('/(Location:|URI:)(.*?)\n/', $header, $matches);
- $newurl = trim(array_pop($matches));
-
- if (strpos($newurl, '/') === 0) {
- $newurl = $old_location_info["scheme"] . "://" . $old_location_info["host"] . $newurl;
- }
-
- if (filter_var($newurl, FILTER_VALIDATE_URL)) {
- $redirects++;
- logger('post_url: redirect ' . $url . ' to ' . $newurl);
- return post_url($newurl, $params, $headers, $redirects, $timeout);
- }
- }
-
- $a->set_curl_code($http_code);
-
- $body = substr($s, strlen($header));
-
- $a->set_curl_headers($header);
-
- curl_close($ch);
-
- $a->save_timestamp($stamp1, 'network');
-
- logger('post_url: end ' . $url, LOGGER_DATA);
-
- return $body;
+ return Network::postURL($url, $params, $headers, $redirects, $timeout);
}
-// 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 = '')
{
- $result = ['status' => $st];
-
- if ($message != '') {
- $result['message'] = $message;
- }
-
- if ($st) {
- logger('xml_status returning non_zero: ' . $st . " message=" . $message);
- }
-
- header("Content-type: text/xml");
-
- $xmldata = ["result" => $result];
-
- echo XML::fromArray($xmldata, $xml);
-
- killme();
+ Network::xmlStatus($st, $message);
}
-/**
- * @brief Send HTTP status header and exit.
- *
- * @param integer $val HTTP status result value
- * @param array $description optional message
- * 'title' => header title
- * 'description' => optional message
- */
-
-/**
- * @brief Send HTTP status header and exit.
- *
- * @param integer $val HTTP status result value
- * @param array $description optional message
- * 'title' => header title
- * 'description' => optional message
- */
function http_status_exit($val, $description = [])
{
- $err = '';
- if ($val >= 400) {
- $err = 'Error';
- if (!isset($description["title"])) {
- $description["title"] = $err." ".$val;
- }
- }
- if ($val >= 200 && $val < 300)
- $err = 'OK';
-
- logger('http_status_exit ' . $val);
- header($_SERVER["SERVER_PROTOCOL"] . ' ' . $val . ' ' . $err);
-
- if (isset($description["title"])) {
- $tpl = get_markup_template('http_status.tpl');
- echo replace_macros(
- $tpl,
- [
- '$title' => $description["title"],
- '$description' => $description["description"]]
- );
- }
-
- killme();
+ Network::httpStatusExit($val, $description);
}
-/**
- * @brief Check URL to se if ts's real
- *
- * 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)
- *
- * @param string $url The URL to be validated
- * @return string|boolean The actual working URL, false else
- */
function validate_url($url)
{
- if (Config::get('system', 'disable_url_validation')) {
- return $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;
- }
-
- /// @TODO Really suppress function outcomes? Why not find them + debug them?
- $h = @parse_url($url);
-
- if ((is_array($h)) && (@dns_get_record($h['host'], DNS_A + DNS_CNAME + DNS_PTR) || filter_var($h['host'], FILTER_VALIDATE_IP) )) {
- return $url;
- }
-
- return false;
+ return Network::validateURL($url);
}
-/**
- * @brief Checks that email is an actual resolvable internet address
- *
- * @param string $addr The email address
- * @return boolean True if it's a valid email address, false if it's not
- */
function validate_email($addr)
{
- if (Config::get('system', 'disable_email_validation')) {
- return true;
- }
-
- if (! strpos($addr, '@')) {
- return false;
- }
-
- $h = substr($addr, strpos($addr, '@') + 1);
-
- if (($h) && (dns_get_record($h, DNS_A + DNS_CNAME + DNS_PTR + DNS_MX) || filter_var($h, FILTER_VALIDATE_IP) )) {
- return true;
- }
- return false;
+ return Network::validateEmail($addr);
}
-/**
- * @brief Check if URL is allowed
- *
- * Check $url against our list of allowed sites,
- * wildcards allowed. If allowed_sites is unset return true;
- *
- * @param string $url URL which get tested
- * @return boolean True if url is allowed otherwise return false
- */
function allowed_url($url)
{
- $h = @parse_url($url);
-
- if (! $h) {
- return false;
- }
-
- $str_allowed = Config::get('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;
+ return Network::allowedURL($url);
}
-/**
- * Checks if the provided url domain is on the domain blocklist.
- * Returns true if it is or malformed URL, false if not.
- *
- * @param string $url The url to check the domain from
- *
- * @return boolean
- */
function blocked_url($url)
{
- $h = @parse_url($url);
-
- if (! $h) {
- return true;
- }
-
- $domain_blocklist = Config::get('system', 'blocklist', []);
- if (! $domain_blocklist) {
- return false;
- }
-
- $host = strtolower($h['host']);
-
- foreach ($domain_blocklist as $domain_block) {
- if (strtolower($domain_block['domain']) == $host) {
- return true;
- }
- }
-
- return false;
+ return Network::blockedURL($url);
}
-/**
- * @brief Check if email address is allowed to register here.
- *
- * Compare against our list (wildcards allowed).
- *
- * @param string $email email address
- * @return boolean False if not allowed, true if allowed
- * or if allowed list is not configured
- */
-function allowed_email($email)
+function allowedEmail($email)
{
- $domain = strtolower(substr($email, strpos($email, '@') + 1));
- if (!$domain) {
- return false;
- }
-
- $str_allowed = Config::get('system', 'allowed_email', '');
- if (!x($str_allowed)) {
- return true;
- }
-
- $allowed = explode(',', $str_allowed);
-
- return allowed_domain($domain, $allowed);
+ return Network::allowedEmail($email);
}
-/**
- * Checks for the existence of a domain in a domain list
- *
- * @brief Checks for the existence of a domain in a domain list
- * @param string $domain
- * @param array $domain_list
- * @return boolean
- */
function allowed_domain($domain, array $domain_list)
{
- $found = false;
-
- foreach ($domain_list as $item) {
- $pat = strtolower(trim($item));
- if (fnmatch($pat, $domain) || ($pat == $domain)) {
- $found = true;
- break;
- }
- }
-
- return $found;
+ return Network::allowedDomain($domain, $domain_list);
}
function avatar_img($email)
{
- $avatar['size'] = 175;
- $avatar['email'] = $email;
- $avatar['url'] = '';
- $avatar['success'] = false;
-
- Addon::callHooks('avatar_lookup', $avatar);
-
- if (! $avatar['success']) {
- $avatar['url'] = System::baseUrl() . '/images/person-175.jpg';
- }
-
- logger('Avatar: ' . $avatar['email'] . ' ' . $avatar['url'], LOGGER_DEBUG);
- return $avatar['url'];
+ return Network::avatarImg($email);
}
function parse_xml_string($s, $strict = true)
{
- // the "strict" parameter is deactivated
-
- /// @todo Move this function to the xml class
- libxml_use_internal_errors(true);
-
- $x = @simplexml_load_string($s);
- if (!$x) {
- logger('libxml: parse: error: ' . $s, 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;
+ return Network::parseXmlString($s, $strict);
}
function scale_external_images($srctext, $include_link = true, $scale_replace = false)
{
- // Suppress "view full size"
- if (intval(Config::get('system', 'no_view_full_size'))) {
- $include_link = false;
- }
-
- // Picture addresses can contain special characters
- $s = htmlspecialchars_decode($srctext);
-
- $matches = null;
- $c = preg_match_all('/\[img.*?\](.*?)\[\/img\]/ism', $s, $matches, PREG_SET_ORDER);
- if ($c) {
- foreach ($matches as $mtch) {
- logger('scale_external_image: ' . $mtch[1]);
-
- $hostname = str_replace('www.', '', substr(System::baseUrl(), strpos(System::baseUrl(), '://') + 3));
- if (stristr($mtch[1], $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[1]);
- } else {
- $scaled = $mtch[1];
- }
- $i = fetch_url($scaled);
- if (! $i) {
- return $srctext;
- }
-
- // guess mimetype from headers or filename
- $type = Image::guessType($mtch[1], true);
-
- if ($i) {
- $Image = new Image($i, $type);
- if ($Image->isValid()) {
- $orig_width = $Image->getWidth();
- $orig_height = $Image->getHeight();
-
- if ($orig_width > 640 || $orig_height > 640) {
- $Image->scaleDown(640);
- $new_width = $Image->getWidth();
- $new_height = $Image->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],
- '[img=' . $new_width . 'x' . $new_height. ']' . $scaled . '[/img]'
- . "\n" . (($include_link)
- ? '[url=' . $mtch[1] . ']' . L10n::t('view full size') . '[/url]' . "\n"
- : ''),
- $s
- );
- logger('scale_external_images: new string: ' . $s, LOGGER_DEBUG);
- }
- }
- }
- }
- }
-
- // replace the special char encoding
- $s = htmlspecialchars($s, ENT_NOQUOTES, 'UTF-8');
- return $s;
+ return Network::scaleExternalImages($srctext, $include_link, $scale_replace);
}
-
function fix_contact_ssl_policy(&$contact, $new_policy)
{
- $ssl_changed = false;
- if ((intval($new_policy) == SSL_POLICY_SELFSIGN || $new_policy === 'self') && strstr($contact['url'], 'https:')) {
- $ssl_changed = true;
- $contact['url'] = str_replace('https:', 'http:', $contact['url']);
- $contact['request'] = str_replace('https:', 'http:', $contact['request']);
- $contact['notify'] = str_replace('https:', 'http:', $contact['notify']);
- $contact['poll'] = str_replace('https:', 'http:', $contact['poll']);
- $contact['confirm'] = str_replace('https:', 'http:', $contact['confirm']);
- $contact['poco'] = str_replace('https:', 'http:', $contact['poco']);
- }
-
- if ((intval($new_policy) == SSL_POLICY_FULL || $new_policy === 'full') && strstr($contact['url'], 'http:')) {
- $ssl_changed = true;
- $contact['url'] = str_replace('http:', 'https:', $contact['url']);
- $contact['request'] = str_replace('http:', 'https:', $contact['request']);
- $contact['notify'] = str_replace('http:', 'https:', $contact['notify']);
- $contact['poll'] = str_replace('http:', 'https:', $contact['poll']);
- $contact['confirm'] = str_replace('http:', 'https:', $contact['confirm']);
- $contact['poco'] = str_replace('http:', 'https:', $contact['poco']);
- }
-
- if ($ssl_changed) {
- $fields = ['url' => $contact['url'], 'request' => $contact['request'],
- 'notify' => $contact['notify'], 'poll' => $contact['poll'],
- 'confirm' => $contact['confirm'], 'poco' => $contact['poco']];
- dba::update('contact', $fields, ['id' => $contact['id']]);
- }
+ Network::fixContactSslPolicy($contact, $new_policy);
}
-/**
- * @brief Remove Google Analytics and other tracking platforms params from URL
- *
- * @param string $url Any user-submitted URL that may contain tracking params
- * @return string The same URL stripped of tracking parameters
- */
function strip_tracking_query_params($url)
{
- $urldata = parse_url($url);
- if (is_string($urldata["query"])) {
- $query = $urldata["query"];
- parse_str($query, $querydata);
-
- if (is_array($querydata)) {
- foreach ($querydata as $param => $value) {
- if (in_array(
- $param,
- [
- "utm_source", "utm_medium", "utm_term", "utm_content", "utm_campaign",
- "wt_mc", "pk_campaign", "pk_kwd", "mc_cid", "mc_eid",
- "fb_action_ids", "fb_action_types", "fb_ref",
- "awesm", "wtrid",
- "woo_campaign", "woo_source", "woo_medium", "woo_content", "woo_term"]
- )
- ) {
- $pair = $param . "=" . urlencode($value);
- $url = str_replace($pair, "", $url);
-
- // Second try: if the url isn't encoded completely
- $pair = $param . "=" . str_replace(" ", "+", $value);
- $url = str_replace($pair, "", $url);
-
- // Third try: Maybey the url isn't encoded at all
- $pair = $param . "=" . $value;
- $url = str_replace($pair, "", $url);
-
- $url = str_replace(["?&", "&&"], ["?", ""], $url);
- }
- }
- }
-
- if (substr($url, -1, 1) == "?") {
- $url = substr($url, 0, -1);
- }
- }
-
- return $url;
+ return Network::stripTrackingQueryParams($url);
}
-/**
- * @brief Returns the original URL of the provided URL
- *
- * This function strips tracking query params and follows redirections, either
- * through HTTP code or meta refresh tags. Stops after 10 redirections.
- *
- * @todo Remove the $fetchbody parameter that generates an extraneous HEAD request
- *
- * @see ParseUrl::getSiteinfo
- *
- * @param string $url A user-submitted URL
- * @param int $depth The current redirection recursion level (internal)
- * @param bool $fetchbody Wether to fetch the body or not after the HEAD requests
- * @return string A canonical URL
- */
function original_url($url, $depth = 1, $fetchbody = false)
{
- $a = get_app();
-
- $url = strip_tracking_query_params($url);
-
- if ($depth > 10) {
- return($url);
- }
-
- $url = trim($url, "'");
-
- $stamp1 = microtime(true);
-
- $ch = curl_init();
- curl_setopt($ch, CURLOPT_URL, $url);
- curl_setopt($ch, CURLOPT_HEADER, 1);
- curl_setopt($ch, CURLOPT_NOBODY, 1);
- curl_setopt($ch, CURLOPT_TIMEOUT, 10);
- curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
- curl_setopt($ch, CURLOPT_USERAGENT, $a->get_useragent());
-
- curl_exec($ch);
- $curl_info = @curl_getinfo($ch);
- $http_code = $curl_info['http_code'];
- curl_close($ch);
-
- $a->save_timestamp($stamp1, "network");
-
- if ($http_code == 0)
- return($url);
-
- if ((($curl_info['http_code'] == "301") || ($curl_info['http_code'] == "302"))
- && (($curl_info['redirect_url'] != "") || ($curl_info['location'] != ""))
- ) {
- if ($curl_info['redirect_url'] != "") {
- return(original_url($curl_info['redirect_url'], ++$depth, $fetchbody));
- } else {
- return(original_url($curl_info['location'], ++$depth, $fetchbody));
- }
- }
-
- // Check for redirects in the meta elements of the body if there are no redirects in the header.
- if (!$fetchbody) {
- return(original_url($url, ++$depth, true));
- }
-
- // if the file is too large then exit
- if ($curl_info["download_content_length"] > 1000000) {
- return($url);
- }
-
- // if it isn't a HTML file then exit
- if (($curl_info["content_type"] != "") && !strstr(strtolower($curl_info["content_type"]), "html")) {
- return($url);
- }
-
- $stamp1 = microtime(true);
-
- $ch = curl_init();
- curl_setopt($ch, CURLOPT_URL, $url);
- curl_setopt($ch, CURLOPT_HEADER, 0);
- curl_setopt($ch, CURLOPT_NOBODY, 0);
- curl_setopt($ch, CURLOPT_TIMEOUT, 10);
- curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
- curl_setopt($ch, CURLOPT_USERAGENT, $a->get_useragent());
-
- $body = curl_exec($ch);
- curl_close($ch);
-
- $a->save_timestamp($stamp1, "network");
-
- if (trim($body) == "") {
- return($url);
- }
-
- // Check for redirect in meta elements
- $doc = new DOMDocument();
- @$doc->loadHTML($body);
-
- $xpath = new DomXPath($doc);
-
- $list = $xpath->query("//meta[@content]");
- foreach ($list as $node) {
- $attr = [];
- if ($node->attributes->length) {
- foreach ($node->attributes as $attribute) {
- $attr[$attribute->name] = $attribute->value;
- }
- }
-
- if (@$attr["http-equiv"] == 'refresh') {
- $path = $attr["content"];
- $pathinfo = explode(";", $path);
- foreach ($pathinfo as $value) {
- if (substr(strtolower($value), 0, 4) == "url=") {
- return(original_url(substr($value, 4), ++$depth));
- }
- }
- }
- }
-
- return $url;
+ return Network::originalURL($url, $depth, $fetchbody);
}
function short_link($url)
{
- require_once 'library/slinky.php';
- $slinky = new Slinky($url);
- $yourls_url = Config::get('yourls', 'url1');
- if ($yourls_url) {
- $yourls_username = Config::get('yourls', 'username1');
- $yourls_password = Config::get('yourls', 'password1');
- $yourls_ssl = Config::get('yourls', 'ssl1');
- $yourls = new Slinky_YourLS();
- $yourls->set('username', $yourls_username);
- $yourls->set('password', $yourls_password);
- $yourls->set('ssl', $yourls_ssl);
- $yourls->set('yourls-url', $yourls_url);
- $slinky->set_cascade([$yourls, new Slinky_Ur1ca(), new Slinky_TinyURL()]);
- } else {
- // setup a cascade of shortening services
- // try to get a short link from these services
- // in the order ur1.ca, tinyurl
- $slinky->set_cascade([new Slinky_Ur1ca(), new Slinky_TinyURL()]);
- }
- return $slinky->short();
+ return Network::shortLink($url);
}
-/**
- * @brief Encodes content to json
- *
- * This function encodes an array to json format
- * and adds an application/json HTTP header to the output.
- * After finishing the process is getting killed.
- *
- * @param array $x The input content
- */
function json_return_and_die($x)
{
- header("content-type: application/json");
- echo json_encode($x);
- killme();
+ Network::jsonReturnAndDie($x);
}
-/**
- * @brief Find the matching part between two url
- *
- * @param string $url1
- * @param string $url2
- * @return string The matching part
- */
function matching_url($url1, $url2)
{
- if (($url1 == "") || ($url2 == "")) {
- return "";
- }
-
- $url1 = normalise_link($url1);
- $url2 = normalise_link($url2);
-
- $parts1 = parse_url($url1);
- $parts2 = parse_url($url2);
-
- if (!isset($parts1["host"]) || !isset($parts2["host"])) {
- return "";
- }
-
- if ($parts1["scheme"] != $parts2["scheme"]) {
- return "";
- }
-
- if ($parts1["host"] != $parts2["host"]) {
- return "";
- }
-
- if ($parts1["port"] != $parts2["port"]) {
- return "";
- }
-
- $match = $parts1["scheme"]."://".$parts1["host"];
-
- if ($parts1["port"]) {
- $match .= ":".$parts1["port"];
- }
-
- $pathparts1 = explode("/", $parts1["path"]);
- $pathparts2 = explode("/", $parts2["path"]);
-
- $i = 0;
- $path = "";
- do {
- $path1 = $pathparts1[$i];
- $path2 = $pathparts2[$i];
-
- if ($path1 == $path2) {
- $path .= $path1."/";
- }
- } while (($path1 == $path2) && ($i++ <= count($pathparts1)));
-
- $match .= $path;
-
- return normalise_link($match);
+ return Network::matchingURL($url1, $url2);
}
-/**
- * @brief Glue url parts together
- *
- * @param array $parsed URL parts
- *
- * @return string The glued URL
- */
function unParseUrl($parsed)
{
- $get = function ($key) use ($parsed) {
- return isset($parsed[$key]) ? $parsed[$key] : null;
- };
-
- $pass = $get('pass');
- $user = $get('user');
- $userinfo = $pass !== null ? "$user:$pass" : $user;
- $port = $get('port');
- $scheme = $get('scheme');
- $query = $get('query');
- $fragment = $get('fragment');
- $authority = ($userinfo !== null ? $userinfo."@" : '') .
- $get('host') .
- ($port ? ":$port" : '');
-
- return (strlen($scheme) ? $scheme.":" : '') .
- (strlen($authority) ? "//".$authority : '') .
- $get('path') .
- (strlen($query) ? "?".$query : '') .
- (strlen($fragment) ? "#".$fragment : '');
+ return Network::unParseURL($parsed);
}
--- /dev/null
+<?php
+/**
+ * @file src/Util/Network.php
+ */
+namespace Friendica\Util;
+
+use Friendica\App;
+use Friendica\Core\Addon;
+use Friendica\Core\L10n;
+use Friendica\Core\System;
+use Friendica\Core\Config;
+use Friendica\Network\Probe;
+use Friendica\Object\Image;
+use Friendica\Util\XML;
+
+require_once 'library/slinky.php';
+
+class Network
+{
+ /**
+ * @brief Curl wrapper
+ *
+ * If binary flag is true, return binary results.
+ * Set the cookiejar argument to a string (e.g. "/tmp/friendica-cookies.txt")
+ * to preserve cookies from one request to the next.
+ *
+ * @param string $url URL to fetch
+ * @param boolean $binary default false
+ * TRUE if asked to return binary results (file download)
+ * @param integer $redirects The recursion counter for internal use - default 0
+ * @param integer $timeout Timeout in seconds, default system config value or 60 seconds
+ * @param string $accept_content supply Accept: header with 'accept_content' as the value
+ * @param string $cookiejar Path to cookie jar file
+ *
+ * @return string The fetched content
+ */
+ public static function fetchURL($url, $binary = false, &$redirects = 0, $timeout = 0, $accept_content = null, $cookiejar = 0)
+ {
+ $ret = self::zFetchURL(
+ $url,
+ $binary,
+ $redirects,
+ ['timeout'=>$timeout,
+ 'accept_content'=>$accept_content,
+ 'cookiejar'=>$cookiejar
+ ]
+ );
+
+ return($ret['body']);
+ }
+
+ /**
+ * @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 The recursion counter for internal use - default 0
+ * @param array $opts (optional parameters) assoziative array with:
+ * 'accept_content' => supply Accept: header with 'accept_content' as the value
+ * 'timeout' => int Timeout in 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
+ * 'nobody' => only return the header
+ * 'cookiejar' => path to cookie jar file
+ *
+ * @return array an assoziative array with:
+ * int 'return_code' => HTTP return code or 0 if timeout or failure
+ * boolean 'success' => boolean true (if HTTP 2xx result) or false
+ * string 'redirect_url' => in case of redirect, content was finally retrieved from this URL
+ * string 'header' => HTTP headers
+ * string 'body' => fetched content
+ */
+ public static function zFetchURL($url, $binary = false, &$redirects = 0, $opts = [])
+ {
+ $ret = ['return_code' => 0, 'success' => false, 'header' => '', 'info' => '', 'body' => ''];
+
+ $stamp1 = microtime(true);
+
+ $a = get_app();
+
+ if (blocked_url($url)) {
+ logger('z_fetch_url: domain of ' . $url . ' is blocked', LOGGER_DATA);
+ return $ret;
+ }
+
+ $ch = @curl_init($url);
+
+ if (($redirects > 8) || (!$ch)) {
+ return $ret;
+ }
+
+ @curl_setopt($ch, CURLOPT_HEADER, true);
+
+ if (x($opts, "cookiejar")) {
+ curl_setopt($ch, CURLOPT_COOKIEJAR, $opts["cookiejar"]);
+ curl_setopt($ch, CURLOPT_COOKIEFILE, $opts["cookiejar"]);
+ }
+
+ // These settings aren't needed. We're following the location already.
+ // @curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
+ // @curl_setopt($ch, CURLOPT_MAXREDIRS, 5);
+
+ if (x($opts, 'accept_content')) {
+ curl_setopt(
+ $ch,
+ CURLOPT_HTTPHEADER,
+ ['Accept: ' . $opts['accept_content']]
+ );
+ }
+
+ @curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+ @curl_setopt($ch, CURLOPT_USERAGENT, $a->get_useragent());
+
+ $range = intval(Config::get('system', 'curl_range_bytes', 0));
+
+ if ($range > 0) {
+ @curl_setopt($ch, CURLOPT_RANGE, '0-' . $range);
+ }
+
+ // Without this setting it seems as if some webservers send compressed content
+ // This seems to confuse curl so that it shows this uncompressed.
+ /// @todo We could possibly set this value to "gzip" or something similar
+ curl_setopt($ch, CURLOPT_ENCODING, '');
+
+ 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, 'timeout')) {
+ @curl_setopt($ch, CURLOPT_TIMEOUT, $opts['timeout']);
+ } else {
+ $curl_time = Config::get('system', 'curl_timeout', 60);
+ @curl_setopt($ch, CURLOPT_TIMEOUT, intval($curl_time));
+ }
+
+ // by default we will allow self-signed certs
+ // but you can override this
+
+ $check_cert = Config::get('system', 'verifyssl');
+ @curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, (($check_cert) ? true : false));
+
+ if ($check_cert) {
+ @curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
+ }
+
+ $proxy = Config::get('system', 'proxy');
+
+ if (strlen($proxy)) {
+ @curl_setopt($ch, CURLOPT_HTTPPROXYTUNNEL, 1);
+ @curl_setopt($ch, CURLOPT_PROXY, $proxy);
+ $proxyuser = @Config::get('system', 'proxyuser');
+
+ if (strlen($proxyuser)) {
+ @curl_setopt($ch, CURLOPT_PROXYUSERPWD, $proxyuser);
+ }
+ }
+
+ if (Config::get('system', 'ipv4_resolve', false)) {
+ curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
+ }
+
+ if ($binary) {
+ @curl_setopt($ch, CURLOPT_BINARYTRANSFER, 1);
+ }
+
+ $a->set_curl_code(0);
+
+ // don't let curl abort the entire application
+ // if it throws any errors.
+
+ $s = @curl_exec($ch);
+ $curl_info = @curl_getinfo($ch);
+
+ // Special treatment for HTTP Code 416
+ // See https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/416
+ if (($curl_info['http_code'] == 416) && ($range > 0)) {
+ @curl_setopt($ch, CURLOPT_RANGE, '');
+ $s = @curl_exec($ch);
+ $curl_info = @curl_getinfo($ch);
+ }
+
+ if (curl_errno($ch) !== CURLE_OK) {
+ logger('fetch_url error fetching ' . $url . ': ' . curl_error($ch), LOGGER_NORMAL);
+ }
+
+ $ret['errno'] = curl_errno($ch);
+
+ $base = $s;
+ $ret['info'] = $curl_info;
+
+ $http_code = $curl_info['http_code'];
+
+ logger('fetch_url ' . $url . ': ' . $http_code . " " . $s, LOGGER_DATA);
+ $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));
+ }
+
+ $a->set_curl_code($http_code);
+ $a->set_curl_content_type($curl_info['content_type']);
+ $a->set_curl_headers($header);
+
+ if ($http_code == 301 || $http_code == 302 || $http_code == 303 || $http_code == 307) {
+ $new_location_info = @parse_url($curl_info['redirect_url']);
+ $old_location_info = @parse_url($curl_info['url']);
+
+ $newurl = $curl_info['redirect_url'];
+
+ if (($new_location_info['path'] == '') && ( $new_location_info['host'] != '')) {
+ $newurl = $new_location_info['scheme'] . '://' . $new_location_info['host'] . $old_location_info['path'];
+ }
+
+ $matches = [];
+
+ if (preg_match('/(Location:|URI:)(.*?)\n/i', $header, $matches)) {
+ $newurl = trim(array_pop($matches));
+ }
+ if (strpos($newurl, '/') === 0) {
+ $newurl = $old_location_info["scheme"]."://".$old_location_info["host"].$newurl;
+ }
+
+ if (filter_var($newurl, FILTER_VALIDATE_URL)) {
+ $redirects++;
+ @curl_close($ch);
+ return z_fetch_url($newurl, $binary, $redirects, $opts);
+ }
+ }
+
+ $a->set_curl_code($http_code);
+ $a->set_curl_content_type($curl_info['content_type']);
+
+ $rc = intval($http_code);
+ $ret['return_code'] = $rc;
+ $ret['success'] = (($rc >= 200 && $rc <= 299) ? true : false);
+ $ret['redirect_url'] = $url;
+
+ if (!$ret['success']) {
+ $ret['error'] = curl_error($ch);
+ $ret['debug'] = $curl_info;
+ logger('z_fetch_url: error: '.$url.': '.$ret['return_code'].' - '.$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);
+
+ $a->save_timestamp($stamp1, 'network');
+
+ return($ret);
+ }
+
+ /**
+ * @brief Send POST request to $url
+ *
+ * @param string $url URL to post
+ * @param mixed $params array of POST variables
+ * @param string $headers HTTP headers
+ * @param integer $redirects Recursion counter for internal use - default = 0
+ * @param integer $timeout The timeout in seconds, default system config value or 60 seconds
+ *
+ * @return string The content
+ */
+ public static function postURL($url, $params, $headers = null, &$redirects = 0, $timeout = 0)
+ {
+ $stamp1 = microtime(true);
+
+ if (blocked_url($url)) {
+ logger('post_url: domain of ' . $url . ' is blocked', LOGGER_DATA);
+ return false;
+ }
+
+ $a = get_app();
+ $ch = curl_init($url);
+
+ if (($redirects > 8) || (!$ch)) {
+ return false;
+ }
+
+ logger('post_url: start ' . $url, LOGGER_DATA);
+
+ curl_setopt($ch, CURLOPT_HEADER, true);
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+ curl_setopt($ch, CURLOPT_POST, 1);
+ curl_setopt($ch, CURLOPT_POSTFIELDS, $params);
+ curl_setopt($ch, CURLOPT_USERAGENT, $a->get_useragent());
+
+ if (Config::get('system', 'ipv4_resolve', false)) {
+ curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
+ }
+
+ if (intval($timeout)) {
+ curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
+ } else {
+ $curl_time = Config::get('system', 'curl_timeout', 60);
+ curl_setopt($ch, CURLOPT_TIMEOUT, intval($curl_time));
+ }
+
+ if (defined('LIGHTTPD')) {
+ if (!is_array($headers)) {
+ $headers = ['Expect:'];
+ } else {
+ if (!in_array('Expect:', $headers)) {
+ array_push($headers, 'Expect:');
+ }
+ }
+ }
+
+ if ($headers) {
+ curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
+ }
+
+ $check_cert = Config::get('system', 'verifyssl');
+ curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, (($check_cert) ? true : false));
+
+ if ($check_cert) {
+ @curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
+ }
+
+ $proxy = Config::get('system', 'proxy');
+
+ if (strlen($proxy)) {
+ curl_setopt($ch, CURLOPT_HTTPPROXYTUNNEL, 1);
+ curl_setopt($ch, CURLOPT_PROXY, $proxy);
+ $proxyuser = Config::get('system', 'proxyuser');
+ if (strlen($proxyuser)) {
+ curl_setopt($ch, CURLOPT_PROXYUSERPWD, $proxyuser);
+ }
+ }
+
+ $a->set_curl_code(0);
+
+ // 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('post_url: result ' . $http_code . ' - ' . $url, LOGGER_DATA);
+
+ $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) {
+ $matches = [];
+ preg_match('/(Location:|URI:)(.*?)\n/', $header, $matches);
+ $newurl = trim(array_pop($matches));
+
+ if (strpos($newurl, '/') === 0) {
+ $newurl = $old_location_info["scheme"] . "://" . $old_location_info["host"] . $newurl;
+ }
+
+ if (filter_var($newurl, FILTER_VALIDATE_URL)) {
+ $redirects++;
+ logger('post_url: redirect ' . $url . ' to ' . $newurl);
+ return post_url($newurl, $params, $headers, $redirects, $timeout);
+ }
+ }
+
+ $a->set_curl_code($http_code);
+
+ $body = substr($s, strlen($header));
+
+ $a->set_curl_headers($header);
+
+ curl_close($ch);
+
+ $a->save_timestamp($stamp1, 'network');
+
+ logger('post_url: end ' . $url, LOGGER_DATA);
+
+ return $body;
+ }
+
+ /**
+ * 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.
+ */
+ public static function xmlStatus($st, $message = '')
+ {
+ $result = ['status' => $st];
+
+ if ($message != '') {
+ $result['message'] = $message;
+ }
+
+ if ($st) {
+ logger('xml_status returning non_zero: ' . $st . " message=" . $message);
+ }
+
+ header("Content-type: text/xml");
+
+ $xmldata = ["result" => $result];
+
+ echo XML::fromArray($xmldata, $xml);
+
+ killme();
+ }
+
+ /**
+ * @brief Send HTTP status header and exit.
+ *
+ * @param integer $val HTTP status result value
+ * @param array $description optional message
+ * 'title' => header title
+ * 'description' => optional message
+ */
+ public static function httpStatusExit($val, $description = [])
+ {
+ $err = '';
+ if ($val >= 400) {
+ $err = 'Error';
+ if (!isset($description["title"])) {
+ $description["title"] = $err." ".$val;
+ }
+ }
+
+ if ($val >= 200 && $val < 300) {
+ $err = 'OK';
+ }
+
+ logger('http_status_exit ' . $val);
+ header($_SERVER["SERVER_PROTOCOL"] . ' ' . $val . ' ' . $err);
+
+ if (isset($description["title"])) {
+ $tpl = get_markup_template('http_status.tpl');
+ echo replace_macros(
+ $tpl,
+ [
+ '$title' => $description["title"],
+ '$description' => $description["description"]]
+ );
+ }
+
+ killme();
+ }
+
+ /**
+ * @brief Check URL to se if ts's real
+ *
+ * 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)
+ *
+ * @param string $url The URL to be validated
+ * @return string|boolean The actual working URL, false else
+ */
+ public static function validateURL($url)
+ {
+ if (Config::get('system', 'disable_url_validation')) {
+ return $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;
+ }
+
+ /// @TODO Really suppress function outcomes? Why not find them + debug them?
+ $h = @parse_url($url);
+
+ if ((is_array($h)) && (@dns_get_record($h['host'], DNS_A + DNS_CNAME + DNS_PTR) || filter_var($h['host'], FILTER_VALIDATE_IP) )) {
+ return $url;
+ }
+
+ return false;
+ }
+
+ /**
+ * @brief Checks that email is an actual resolvable internet address
+ *
+ * @param string $addr The email address
+ * @return boolean True if it's a valid email address, false if it's not
+ */
+ public static function validateEmail($addr)
+ {
+ if (Config::get('system', 'disable_email_validation')) {
+ return true;
+ }
+
+ if (! strpos($addr, '@')) {
+ return false;
+ }
+
+ $h = substr($addr, strpos($addr, '@') + 1);
+
+ if (($h) && (dns_get_record($h, DNS_A + DNS_CNAME + DNS_PTR + DNS_MX) || filter_var($h, FILTER_VALIDATE_IP) )) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * @brief Check if URL is allowed
+ *
+ * Check $url against our list of allowed sites,
+ * wildcards allowed. If allowed_sites is unset return true;
+ *
+ * @param string $url URL which get tested
+ * @return boolean True if url is allowed otherwise return false
+ */
+ public static function allowedURL($url)
+ {
+ $h = @parse_url($url);
+
+ if (! $h) {
+ return false;
+ }
+
+ $str_allowed = Config::get('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;
+ }
+
+ /**
+ * Checks if the provided url domain is on the domain blocklist.
+ * Returns true if it is or malformed URL, false if not.
+ *
+ * @param string $url The url to check the domain from
+ *
+ * @return boolean
+ */
+ public static function blockedURL($url)
+ {
+ $h = @parse_url($url);
+
+ if (! $h) {
+ return true;
+ }
+
+ $domain_blocklist = Config::get('system', 'blocklist', []);
+ if (! $domain_blocklist) {
+ return false;
+ }
+
+ $host = strtolower($h['host']);
+
+ foreach ($domain_blocklist as $domain_block) {
+ if (strtolower($domain_block['domain']) == $host) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * @brief Check if email address is allowed to register here.
+ *
+ * Compare against our list (wildcards allowed).
+ *
+ * @param string $email email address
+ * @return boolean False if not allowed, true if allowed
+ * or if allowed list is not configured
+ */
+ public static function allowedEmail($email)
+ {
+ $domain = strtolower(substr($email, strpos($email, '@') + 1));
+ if (!$domain) {
+ return false;
+ }
+
+ $str_allowed = Config::get('system', 'allowed_email', '');
+ if (!x($str_allowed)) {
+ return true;
+ }
+
+ $allowed = explode(',', $str_allowed);
+
+ return allowed_domain($domain, $allowed);
+ }
+
+ /**
+ * Checks for the existence of a domain in a domain list
+ *
+ * @brief Checks for the existence of a domain in a domain list
+ * @param string $domain
+ * @param array $domain_list
+ * @return boolean
+ */
+ public static function allowedDomain($domain, array $domain_list)
+ {
+ $found = false;
+
+ foreach ($domain_list as $item) {
+ $pat = strtolower(trim($item));
+ if (fnmatch($pat, $domain) || ($pat == $domain)) {
+ $found = true;
+ break;
+ }
+ }
+
+ return $found;
+ }
+
+ public static function avatarImg($email)
+ {
+ $avatar['size'] = 175;
+ $avatar['email'] = $email;
+ $avatar['url'] = '';
+ $avatar['success'] = false;
+
+ Addon::callHooks('avatar_lookup', $avatar);
+
+ if (! $avatar['success']) {
+ $avatar['url'] = System::baseUrl() . '/images/person-175.jpg';
+ }
+
+ logger('Avatar: ' . $avatar['email'] . ' ' . $avatar['url'], LOGGER_DEBUG);
+ return $avatar['url'];
+ }
+
+ public static function parseXmlString($s, $strict = true)
+ {
+ // the "strict" parameter is deactivated
+
+ /// @todo Move this function to the xml class
+ libxml_use_internal_errors(true);
+
+ $x = @simplexml_load_string($s);
+ if (!$x) {
+ logger('libxml: parse: error: ' . $s, 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;
+ }
+
+ public static function scaleExternalImages($srctext, $include_link = true, $scale_replace = false)
+ {
+ // Suppress "view full size"
+ if (intval(Config::get('system', 'no_view_full_size'))) {
+ $include_link = false;
+ }
+
+ // Picture addresses can contain special characters
+ $s = htmlspecialchars_decode($srctext);
+
+ $matches = null;
+ $c = preg_match_all('/\[img.*?\](.*?)\[\/img\]/ism', $s, $matches, PREG_SET_ORDER);
+ if ($c) {
+ foreach ($matches as $mtch) {
+ logger('scale_external_image: ' . $mtch[1]);
+
+ $hostname = str_replace('www.', '', substr(System::baseUrl(), strpos(System::baseUrl(), '://') + 3));
+ if (stristr($mtch[1], $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[1]);
+ } else {
+ $scaled = $mtch[1];
+ }
+ $i = fetch_url($scaled);
+ if (! $i) {
+ return $srctext;
+ }
+
+ // guess mimetype from headers or filename
+ $type = Image::guessType($mtch[1], true);
+
+ if ($i) {
+ $Image = new Image($i, $type);
+ if ($Image->isValid()) {
+ $orig_width = $Image->getWidth();
+ $orig_height = $Image->getHeight();
+
+ if ($orig_width > 640 || $orig_height > 640) {
+ $Image->scaleDown(640);
+ $new_width = $Image->getWidth();
+ $new_height = $Image->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],
+ '[img=' . $new_width . 'x' . $new_height. ']' . $scaled . '[/img]'
+ . "\n" . (($include_link)
+ ? '[url=' . $mtch[1] . ']' . L10n::t('view full size') . '[/url]' . "\n"
+ : ''),
+ $s
+ );
+ logger('scale_external_images: new string: ' . $s, LOGGER_DEBUG);
+ }
+ }
+ }
+ }
+ }
+
+ // replace the special char encoding
+ $s = htmlspecialchars($s, ENT_NOQUOTES, 'UTF-8');
+ return $s;
+ }
+
+ public static function fixContactSslPolicy(&$contact, $new_policy)
+ {
+ $ssl_changed = false;
+ if ((intval($new_policy) == SSL_POLICY_SELFSIGN || $new_policy === 'self') && strstr($contact['url'], 'https:')) {
+ $ssl_changed = true;
+ $contact['url'] = str_replace('https:', 'http:', $contact['url']);
+ $contact['request'] = str_replace('https:', 'http:', $contact['request']);
+ $contact['notify'] = str_replace('https:', 'http:', $contact['notify']);
+ $contact['poll'] = str_replace('https:', 'http:', $contact['poll']);
+ $contact['confirm'] = str_replace('https:', 'http:', $contact['confirm']);
+ $contact['poco'] = str_replace('https:', 'http:', $contact['poco']);
+ }
+
+ if ((intval($new_policy) == SSL_POLICY_FULL || $new_policy === 'full') && strstr($contact['url'], 'http:')) {
+ $ssl_changed = true;
+ $contact['url'] = str_replace('http:', 'https:', $contact['url']);
+ $contact['request'] = str_replace('http:', 'https:', $contact['request']);
+ $contact['notify'] = str_replace('http:', 'https:', $contact['notify']);
+ $contact['poll'] = str_replace('http:', 'https:', $contact['poll']);
+ $contact['confirm'] = str_replace('http:', 'https:', $contact['confirm']);
+ $contact['poco'] = str_replace('http:', 'https:', $contact['poco']);
+ }
+
+ if ($ssl_changed) {
+ $fields = ['url' => $contact['url'], 'request' => $contact['request'],
+ 'notify' => $contact['notify'], 'poll' => $contact['poll'],
+ 'confirm' => $contact['confirm'], 'poco' => $contact['poco']];
+ dba::update('contact', $fields, ['id' => $contact['id']]);
+ }
+ }
+
+ /**
+ * @brief Remove Google Analytics and other tracking platforms params from URL
+ *
+ * @param string $url Any user-submitted URL that may contain tracking params
+ * @return string The same URL stripped of tracking parameters
+ */
+ public static function stripTrackingQueryParams($url)
+ {
+ $urldata = parse_url($url);
+ if (is_string($urldata["query"])) {
+ $query = $urldata["query"];
+ parse_str($query, $querydata);
+
+ if (is_array($querydata)) {
+ foreach ($querydata as $param => $value) {
+ if (in_array(
+ $param,
+ [
+ "utm_source", "utm_medium", "utm_term", "utm_content", "utm_campaign",
+ "wt_mc", "pk_campaign", "pk_kwd", "mc_cid", "mc_eid",
+ "fb_action_ids", "fb_action_types", "fb_ref",
+ "awesm", "wtrid",
+ "woo_campaign", "woo_source", "woo_medium", "woo_content", "woo_term"]
+ )
+ ) {
+ $pair = $param . "=" . urlencode($value);
+ $url = str_replace($pair, "", $url);
+
+ // Second try: if the url isn't encoded completely
+ $pair = $param . "=" . str_replace(" ", "+", $value);
+ $url = str_replace($pair, "", $url);
+
+ // Third try: Maybey the url isn't encoded at all
+ $pair = $param . "=" . $value;
+ $url = str_replace($pair, "", $url);
+
+ $url = str_replace(["?&", "&&"], ["?", ""], $url);
+ }
+ }
+ }
+
+ if (substr($url, -1, 1) == "?") {
+ $url = substr($url, 0, -1);
+ }
+ }
+
+ return $url;
+ }
+
+ /**
+ * @brief Returns the original URL of the provided URL
+ *
+ * This function strips tracking query params and follows redirections, either
+ * through HTTP code or meta refresh tags. Stops after 10 redirections.
+ *
+ * @todo Remove the $fetchbody parameter that generates an extraneous HEAD request
+ *
+ * @see ParseUrl::getSiteinfo
+ *
+ * @param string $url A user-submitted URL
+ * @param int $depth The current redirection recursion level (internal)
+ * @param bool $fetchbody Wether to fetch the body or not after the HEAD requests
+ * @return string A canonical URL
+ */
+ public static function originalURL($url, $depth = 1, $fetchbody = false)
+ {
+ $a = get_app();
+
+ $url = strip_tracking_query_params($url);
+
+ if ($depth > 10) {
+ return($url);
+ }
+
+ $url = trim($url, "'");
+
+ $stamp1 = microtime(true);
+
+ $ch = curl_init();
+ curl_setopt($ch, CURLOPT_URL, $url);
+ curl_setopt($ch, CURLOPT_HEADER, 1);
+ curl_setopt($ch, CURLOPT_NOBODY, 1);
+ curl_setopt($ch, CURLOPT_TIMEOUT, 10);
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+ curl_setopt($ch, CURLOPT_USERAGENT, $a->get_useragent());
+
+ curl_exec($ch);
+ $curl_info = @curl_getinfo($ch);
+ $http_code = $curl_info['http_code'];
+ curl_close($ch);
+
+ $a->save_timestamp($stamp1, "network");
+
+ if ($http_code == 0) {
+ return($url);
+ }
+
+ if ((($curl_info['http_code'] == "301") || ($curl_info['http_code'] == "302"))
+ && (($curl_info['redirect_url'] != "") || ($curl_info['location'] != ""))
+ ) {
+ if ($curl_info['redirect_url'] != "") {
+ return(original_url($curl_info['redirect_url'], ++$depth, $fetchbody));
+ } else {
+ return(original_url($curl_info['location'], ++$depth, $fetchbody));
+ }
+ }
+
+ // Check for redirects in the meta elements of the body if there are no redirects in the header.
+ if (!$fetchbody) {
+ return(original_url($url, ++$depth, true));
+ }
+
+ // if the file is too large then exit
+ if ($curl_info["download_content_length"] > 1000000) {
+ return($url);
+ }
+
+ // if it isn't a HTML file then exit
+ if (($curl_info["content_type"] != "") && !strstr(strtolower($curl_info["content_type"]), "html")) {
+ return($url);
+ }
+
+ $stamp1 = microtime(true);
+
+ $ch = curl_init();
+ curl_setopt($ch, CURLOPT_URL, $url);
+ curl_setopt($ch, CURLOPT_HEADER, 0);
+ curl_setopt($ch, CURLOPT_NOBODY, 0);
+ curl_setopt($ch, CURLOPT_TIMEOUT, 10);
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+ curl_setopt($ch, CURLOPT_USERAGENT, $a->get_useragent());
+
+ $body = curl_exec($ch);
+ curl_close($ch);
+
+ $a->save_timestamp($stamp1, "network");
+
+ if (trim($body) == "") {
+ return($url);
+ }
+
+ // Check for redirect in meta elements
+ $doc = new DOMDocument();
+ @$doc->loadHTML($body);
+
+ $xpath = new DomXPath($doc);
+
+ $list = $xpath->query("//meta[@content]");
+ foreach ($list as $node) {
+ $attr = [];
+ if ($node->attributes->length) {
+ foreach ($node->attributes as $attribute) {
+ $attr[$attribute->name] = $attribute->value;
+ }
+ }
+
+ if (@$attr["http-equiv"] == 'refresh') {
+ $path = $attr["content"];
+ $pathinfo = explode(";", $path);
+ foreach ($pathinfo as $value) {
+ if (substr(strtolower($value), 0, 4) == "url=") {
+ return(original_url(substr($value, 4), ++$depth));
+ }
+ }
+ }
+ }
+
+ return $url;
+ }
+
+ public static function shortLink($url)
+ {
+ $slinky = new Slinky($url);
+ $yourls_url = Config::get('yourls', 'url1');
+ if ($yourls_url) {
+ $yourls_username = Config::get('yourls', 'username1');
+ $yourls_password = Config::get('yourls', 'password1');
+ $yourls_ssl = Config::get('yourls', 'ssl1');
+ $yourls = new Slinky_YourLS();
+ $yourls->set('username', $yourls_username);
+ $yourls->set('password', $yourls_password);
+ $yourls->set('ssl', $yourls_ssl);
+ $yourls->set('yourls-url', $yourls_url);
+ $slinky->set_cascade([$yourls, new Slinky_Ur1ca(), new Slinky_TinyURL()]);
+ } else {
+ // setup a cascade of shortening services
+ // try to get a short link from these services
+ // in the order ur1.ca, tinyurl
+ $slinky->set_cascade([new Slinky_Ur1ca(), new Slinky_TinyURL()]);
+ }
+ return $slinky->short();
+ }
+
+ /**
+ * @brief Encodes content to json
+ *
+ * This function encodes an array to json format
+ * and adds an application/json HTTP header to the output.
+ * After finishing the process is getting killed.
+ *
+ * @param array $x The input content
+ */
+ public static function jsonReturnAndDie($x)
+ {
+ header("content-type: application/json");
+ echo json_encode($x);
+ killme();
+ }
+
+ /**
+ * @brief Find the matching part between two url
+ *
+ * @param string $url1
+ * @param string $url2
+ * @return string The matching part
+ */
+ public static function matchingURL($url1, $url2)
+ {
+ if (($url1 == "") || ($url2 == "")) {
+ return "";
+ }
+
+ $url1 = normalise_link($url1);
+ $url2 = normalise_link($url2);
+
+ $parts1 = parse_url($url1);
+ $parts2 = parse_url($url2);
+
+ if (!isset($parts1["host"]) || !isset($parts2["host"])) {
+ return "";
+ }
+
+ if ($parts1["scheme"] != $parts2["scheme"]) {
+ return "";
+ }
+
+ if ($parts1["host"] != $parts2["host"]) {
+ return "";
+ }
+
+ if ($parts1["port"] != $parts2["port"]) {
+ return "";
+ }
+
+ $match = $parts1["scheme"]."://".$parts1["host"];
+
+ if ($parts1["port"]) {
+ $match .= ":".$parts1["port"];
+ }
+
+ $pathparts1 = explode("/", $parts1["path"]);
+ $pathparts2 = explode("/", $parts2["path"]);
+
+ $i = 0;
+ $path = "";
+ do {
+ $path1 = $pathparts1[$i];
+ $path2 = $pathparts2[$i];
+
+ if ($path1 == $path2) {
+ $path .= $path1."/";
+ }
+ } while (($path1 == $path2) && ($i++ <= count($pathparts1)));
+
+ $match .= $path;
+
+ return normalise_link($match);
+ }
+
+ /**
+ * @brief Glue url parts together
+ *
+ * @param array $parsed URL parts
+ *
+ * @return string The glued URL
+ */
+ public static function unParseURL($parsed)
+ {
+ $get = function ($key) use ($parsed) {
+ return isset($parsed[$key]) ? $parsed[$key] : null;
+ };
+
+ $pass = $get('pass');
+ $user = $get('user');
+ $userinfo = $pass !== null ? "$user:$pass" : $user;
+ $port = $get('port');
+ $scheme = $get('scheme');
+ $query = $get('query');
+ $fragment = $get('fragment');
+ $authority = ($userinfo !== null ? $userinfo."@" : '') .
+ $get('host') .
+ ($port ? ":$port" : '');
+
+ return (strlen($scheme) ? $scheme.":" : '') .
+ (strlen($authority) ? "//".$authority : '') .
+ $get('path') .
+ (strlen($query) ? "?".$query : '') .
+ (strlen($fragment) ? "#".$fragment : '');
+ }
+}