]> git.mxchange.org Git - friendica.git/blob - include/network.php
Issue-#3873
[friendica.git] / include / network.php
1 <?php
2
3 /**
4  * @file include/network.php
5  */
6
7 use Friendica\App;
8 use Friendica\Core\System;
9 use Friendica\Core\Config;
10 use Friendica\Network\Probe;
11
12 require_once("include/xml.php");
13
14 /**
15  * @brief Curl wrapper
16  *
17  * If binary flag is true, return binary results.
18  * Set the cookiejar argument to a string (e.g. "/tmp/friendica-cookies.txt")
19  * to preserve cookies from one request to the next.
20  *
21  * @param string $url URL to fetch
22  * @param boolean $binary default false
23  *    TRUE if asked to return binary results (file download)
24  * @param integer $redirects The recursion counter for internal use - default 0
25  * @param integer $timeout Timeout in seconds, default system config value or 60 seconds
26  * @param string $accept_content supply Accept: header with 'accept_content' as the value
27  * @param string $cookiejar Path to cookie jar file
28  *
29  * @return string The fetched content
30  */
31 function fetch_url($url,$binary = false, &$redirects = 0, $timeout = 0, $accept_content=Null, $cookiejar = 0) {
32
33         $ret = z_fetch_url(
34                 $url,
35                 $binary,
36                 $redirects,
37                 array('timeout'=>$timeout,
38                 'accept_content'=>$accept_content,
39                 'cookiejar'=>$cookiejar
40                 ));
41
42         return($ret['body']);
43 }
44
45 /**
46  * @brief fetches an URL.
47  *
48  * @param string $url URL to fetch
49  * @param boolean $binary default false
50  *    TRUE if asked to return binary results (file download)
51  * @param int $redirects The recursion counter for internal use - default 0
52  * @param array $opts (optional parameters) assoziative array with:
53  *    'accept_content' => supply Accept: header with 'accept_content' as the value
54  *    'timeout' => int Timeout in seconds, default system config value or 60 seconds
55  *    'http_auth' => username:password
56  *    'novalidate' => do not validate SSL certs, default is to validate using our CA list
57  *    'nobody' => only return the header
58  *    'cookiejar' => path to cookie jar file
59  *
60  * @return array an assoziative array with:
61  *    int 'return_code' => HTTP return code or 0 if timeout or failure
62  *    boolean 'success' => boolean true (if HTTP 2xx result) or false
63  *    string 'redirect_url' => in case of redirect, content was finally retrieved from this URL
64  *    string 'header' => HTTP headers
65  *    string 'body' => fetched content
66  */
67 function z_fetch_url($url, $binary = false, &$redirects = 0, $opts = array()) {
68         $ret = array('return_code' => 0, 'success' => false, 'header' => '', 'info' => '', 'body' => '');
69
70         $stamp1 = microtime(true);
71
72         $a = get_app();
73
74         if (blocked_url($url)) {
75                 logger('z_fetch_url: domain of ' . $url . ' is blocked', LOGGER_DATA);
76                 return $ret;
77         }
78
79         $ch = @curl_init($url);
80
81         if (($redirects > 8) || (!$ch)) {
82                 return $ret;
83         }
84
85         @curl_setopt($ch, CURLOPT_HEADER, true);
86
87         if (x($opts, "cookiejar")) {
88                 curl_setopt($ch, CURLOPT_COOKIEJAR, $opts["cookiejar"]);
89                 curl_setopt($ch, CURLOPT_COOKIEFILE, $opts["cookiejar"]);
90         }
91
92 // These settings aren't needed. We're following the location already.
93 //      @curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
94 //      @curl_setopt($ch, CURLOPT_MAXREDIRS, 5);
95
96         if (x($opts, 'accept_content')) {
97                 curl_setopt($ch, CURLOPT_HTTPHEADER, array(
98                         'Accept: ' . $opts['accept_content']
99                 ));
100         }
101
102         @curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
103         @curl_setopt($ch, CURLOPT_USERAGENT, $a->get_useragent());
104
105         $range = intval(Config::get('system', 'curl_range_bytes', 0));
106
107         if ($range > 0) {
108                 @curl_setopt($ch, CURLOPT_RANGE, '0-' . $range);
109         }
110
111         // Without this setting it seems as if some webservers send compressed content
112         // This seems to confuse curl so that it shows this uncompressed.
113         /// @todo  We could possibly set this value to "gzip" or something similar
114         curl_setopt($ch, CURLOPT_ENCODING, '');
115
116         if (x($opts, 'headers')) {
117                 @curl_setopt($ch, CURLOPT_HTTPHEADER, $opts['headers']);
118         }
119
120         if (x($opts, 'nobody')) {
121                 @curl_setopt($ch, CURLOPT_NOBODY, $opts['nobody']);
122         }
123
124         if (x($opts, 'timeout')) {
125                 @curl_setopt($ch, CURLOPT_TIMEOUT, $opts['timeout']);
126         } else {
127                 $curl_time = intval(Config::get('system', 'curl_timeout'));
128                 @curl_setopt($ch, CURLOPT_TIMEOUT, (($curl_time !== false) ? $curl_time : 60));
129         }
130
131         // by default we will allow self-signed certs
132         // but you can override this
133
134         $check_cert = Config::get('system', 'verifyssl');
135         @curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, (($check_cert) ? true : false));
136
137         if ($check_cert) {
138                 @curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
139         }
140
141         $proxy = Config::get('system', 'proxy');
142
143         if (strlen($proxy)) {
144                 @curl_setopt($ch, CURLOPT_HTTPPROXYTUNNEL, 1);
145                 @curl_setopt($ch, CURLOPT_PROXY, $proxy);
146                 $proxyuser = @Config::get('system', 'proxyuser');
147
148                 if (strlen($proxyuser)) {
149                         @curl_setopt($ch, CURLOPT_PROXYUSERPWD, $proxyuser);
150                 }
151         }
152
153         if (Config::get('system', 'ipv4_resolve', false)) {
154                 curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
155         }
156
157         if ($binary) {
158                 @curl_setopt($ch, CURLOPT_BINARYTRANSFER, 1);
159         }
160
161         $a->set_curl_code(0);
162
163         // don't let curl abort the entire application
164         // if it throws any errors.
165
166         $s = @curl_exec($ch);
167         $curl_info = @curl_getinfo($ch);
168
169         // Special treatment for HTTP Code 416
170         // See https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/416
171         if (($curl_info['http_code'] == 416) && ($range > 0)) {
172                 @curl_setopt($ch, CURLOPT_RANGE, '');
173                 $s = @curl_exec($ch);
174                 $curl_info = @curl_getinfo($ch);
175         }
176
177         if (curl_errno($ch) !== CURLE_OK) {
178                 logger('fetch_url error fetching ' . $url . ': ' . curl_error($ch), LOGGER_NORMAL);
179         }
180
181         $ret['errno'] = curl_errno($ch);
182
183         $base = $s;
184         $ret['info'] = $curl_info;
185
186         $http_code = $curl_info['http_code'];
187
188         logger('fetch_url ' . $url . ': ' . $http_code . " " . $s, LOGGER_DATA);
189         $header = '';
190
191         // Pull out multiple headers, e.g. proxy and continuation headers
192         // allow for HTTP/2.x without fixing code
193
194         while (preg_match('/^HTTP\/[1-2].+? [1-5][0-9][0-9]/', $base)) {
195                 $chunk = substr($base, 0, strpos($base,"\r\n\r\n") + 4);
196                 $header .= $chunk;
197                 $base = substr($base, strlen($chunk));
198         }
199
200         $a->set_curl_code($http_code);
201         $a->set_curl_content_type($curl_info['content_type']);
202         $a->set_curl_headers($header);
203
204         if ($http_code == 301 || $http_code == 302 || $http_code == 303 || $http_code == 307) {
205                 $new_location_info = @parse_url($curl_info['redirect_url']);
206                 $old_location_info = @parse_url($curl_info['url']);
207
208                 $newurl = $curl_info['redirect_url'];
209
210                 if (($new_location_info['path'] == '') && ( $new_location_info['host'] != '')) {
211                         $newurl = $new_location_info['scheme'] . '://' . $new_location_info['host'] . $old_location_info['path'];
212                 }
213
214                 $matches = array();
215
216                 if (preg_match('/(Location:|URI:)(.*?)\n/i', $header, $matches)) {
217                         $newurl = trim(array_pop($matches));
218                 }
219                 if (strpos($newurl,'/') === 0) {
220                         $newurl = $old_location_info["scheme"]."://".$old_location_info["host"].$newurl;
221                 }
222
223                 if (filter_var($newurl, FILTER_VALIDATE_URL)) {
224                         $redirects++;
225                         @curl_close($ch);
226                         return z_fetch_url($newurl, $binary, $redirects, $opts);
227                 }
228         }
229
230         $a->set_curl_code($http_code);
231         $a->set_curl_content_type($curl_info['content_type']);
232
233         $body = substr($s, strlen($header));
234
235         $rc = intval($http_code);
236         $ret['return_code'] = $rc;
237         $ret['success'] = (($rc >= 200 && $rc <= 299) ? true : false);
238         $ret['redirect_url'] = $url;
239
240         if (!$ret['success']) {
241                 $ret['error'] = curl_error($ch);
242                 $ret['debug'] = $curl_info;
243                 logger('z_fetch_url: error: '.$url.': '.$ret['return_code'].' - '.$ret['error'], LOGGER_DEBUG);
244                 logger('z_fetch_url: debug: '.print_r($curl_info, true), LOGGER_DATA);
245         }
246
247         $ret['body'] = substr($s, strlen($header));
248         $ret['header'] = $header;
249
250         if (x($opts, 'debug')) {
251                 $ret['debug'] = $curl_info;
252         }
253
254         @curl_close($ch);
255
256         $a->save_timestamp($stamp1, 'network');
257
258         return($ret);
259 }
260
261 /**
262  * @brief Send POST request to $url
263  *
264  * @param string $url URL to post
265  * @param mixed $params array of POST variables
266  * @param string $headers HTTP headers
267  * @param integer $redirects Recursion counter for internal use - default = 0
268  * @param integer $timeout The timeout in seconds, default system config value or 60 seconds
269  *
270  * @return string The content
271  */
272 function post_url($url, $params, $headers = null, &$redirects = 0, $timeout = 0) {
273         $stamp1 = microtime(true);
274
275         if (blocked_url($url)) {
276                 logger('post_url: domain of ' . $url . ' is blocked', LOGGER_DATA);
277                 return false;
278         }
279
280         $a = get_app();
281         $ch = curl_init($url);
282
283         if (($redirects > 8) || (!$ch)) {
284                 return false;
285         }
286
287         logger('post_url: start ' . $url, LOGGER_DATA);
288
289         curl_setopt($ch, CURLOPT_HEADER, true);
290         curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
291         curl_setopt($ch, CURLOPT_POST, 1);
292         curl_setopt($ch, CURLOPT_POSTFIELDS, $params);
293         curl_setopt($ch, CURLOPT_USERAGENT, $a->get_useragent());
294
295         if (Config::get('system', 'ipv4_resolve', false)) {
296                 curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
297         }
298
299         if (intval($timeout)) {
300                 curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
301         } else {
302                 $curl_time = intval(Config::get('system', 'curl_timeout'));
303                 curl_setopt($ch, CURLOPT_TIMEOUT, (($curl_time !== false) ? $curl_time : 60));
304         }
305
306         if (defined('LIGHTTPD')) {
307                 if (!is_array($headers)) {
308                         $headers = array('Expect:');
309                 } else {
310                         if (!in_array('Expect:', $headers)) {
311                                 array_push($headers, 'Expect:');
312                         }
313                 }
314         }
315
316         if ($headers) {
317                 curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
318         }
319
320         $check_cert = Config::get('system', 'verifyssl');
321         curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, (($check_cert) ? true : false));
322
323         if ($check_cert) {
324                 @curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
325         }
326
327         $proxy = Config::get('system', 'proxy');
328
329         if (strlen($proxy)) {
330                 curl_setopt($ch, CURLOPT_HTTPPROXYTUNNEL, 1);
331                 curl_setopt($ch, CURLOPT_PROXY, $proxy);
332                 $proxyuser = Config::get('system', 'proxyuser');
333                 if (strlen($proxyuser)) {
334                         curl_setopt($ch, CURLOPT_PROXYUSERPWD, $proxyuser);
335                 }
336         }
337
338         $a->set_curl_code(0);
339
340         // don't let curl abort the entire application
341         // if it throws any errors.
342
343         $s = @curl_exec($ch);
344
345         $base = $s;
346         $curl_info = curl_getinfo($ch);
347         $http_code = $curl_info['http_code'];
348
349         logger('post_url: result ' . $http_code . ' - ' . $url, LOGGER_DATA);
350
351         $header = '';
352
353         // Pull out multiple headers, e.g. proxy and continuation headers
354         // allow for HTTP/2.x without fixing code
355
356         while (preg_match('/^HTTP\/[1-2].+? [1-5][0-9][0-9]/', $base)) {
357                 $chunk = substr($base, 0, strpos($base, "\r\n\r\n") + 4);
358                 $header .= $chunk;
359                 $base = substr($base, strlen($chunk));
360         }
361
362         if ($http_code == 301 || $http_code == 302 || $http_code == 303 || $http_code == 307) {
363                 $matches = array();
364                 preg_match('/(Location:|URI:)(.*?)\n/', $header, $matches);
365                 $newurl = trim(array_pop($matches));
366
367                 if (strpos($newurl, '/') === 0) {
368                         $newurl = $old_location_info["scheme"] . "://" . $old_location_info["host"] . $newurl;
369                 }
370
371                 if (filter_var($newurl, FILTER_VALIDATE_URL)) {
372                         $redirects++;
373                         logger('post_url: redirect ' . $url . ' to ' . $newurl);
374                         return post_url($newurl, $params, $headers, $redirects, $timeout);
375                 }
376         }
377
378         $a->set_curl_code($http_code);
379
380         $body = substr($s, strlen($header));
381
382         $a->set_curl_headers($header);
383
384         curl_close($ch);
385
386         $a->save_timestamp($stamp1, 'network');
387
388         logger('post_url: end ' . $url, LOGGER_DATA);
389
390         return $body;
391 }
392
393 // Generic XML return
394 // Outputs a basic dfrn XML status structure to STDOUT, with a <status> variable
395 // of $st and an optional text <message> of $message and terminates the current process.
396
397 function xml_status($st, $message = '') {
398
399         $result = array('status' => $st);
400
401         if ($message != '') {
402                 $result['message'] = $message;
403         }
404
405         if ($st) {
406                 logger('xml_status returning non_zero: ' . $st . " message=" . $message);
407         }
408
409         header("Content-type: text/xml");
410
411         $xmldata = array("result" => $result);
412
413         echo xml::from_array($xmldata, $xml);
414
415         killme();
416 }
417
418 /**
419  * @brief Send HTTP status header and exit.
420  *
421  * @param integer $val HTTP status result value
422  * @param array $description optional message
423  *    'title' => header title
424  *    'description' => optional message
425  */
426
427 /**
428  * @brief Send HTTP status header and exit.
429  *
430  * @param integer $val HTTP status result value
431  * @param array $description optional message
432  *    'title' => header title
433  *    'description' => optional message
434  */
435 function http_status_exit($val, $description = array()) {
436         $err = '';
437         if ($val >= 400) {
438                 $err = 'Error';
439                 if (!isset($description["title"]))
440                         $description["title"] = $err." ".$val;
441         }
442         if ($val >= 200 && $val < 300)
443                 $err = 'OK';
444
445         logger('http_status_exit ' . $val);
446         header($_SERVER["SERVER_PROTOCOL"] . ' ' . $val . ' ' . $err);
447
448         if (isset($description["title"])) {
449                 $tpl = get_markup_template('http_status.tpl');
450                 echo replace_macros($tpl, array('$title' => $description["title"],
451                                                 '$description' => $description["description"]));
452         }
453
454         killme();
455
456 }
457
458 /**
459  * @brief Check URL to se if ts's real
460  *
461  * Take a URL from the wild, prepend http:// if necessary
462  * and check DNS to see if it's real (or check if is a valid IP address)
463  *
464  * @param string $url The URL to be validated
465  * @return boolean True if it's a valid URL, fals if something wrong with it
466  */
467 function validate_url(&$url) {
468         if (Config::get('system','disable_url_validation'))
469                 return true;
470
471         // no naked subdomains (allow localhost for tests)
472         if (strpos($url,'.') === false && strpos($url,'/localhost/') === false)
473                 return false;
474
475         if (substr($url,0,4) != 'http')
476                 $url = 'http://' . $url;
477
478         /// @TODO Really supress function outcomes? Why not find them + debug them?
479         $h = @parse_url($url);
480
481         if ((is_array($h)) && (dns_get_record($h['host'], DNS_A + DNS_CNAME + DNS_PTR) || filter_var($h['host'], FILTER_VALIDATE_IP) )) {
482                 return true;
483         }
484
485         return false;
486 }
487
488 /**
489  * @brief Checks that email is an actual resolvable internet address
490  *
491  * @param string $addr The email address
492  * @return boolean True if it's a valid email address, false if it's not
493  */
494 function validate_email($addr) {
495
496         if (Config::get('system','disable_email_validation'))
497                 return true;
498
499         if (! strpos($addr,'@'))
500                 return false;
501         $h = substr($addr,strpos($addr,'@') + 1);
502
503         if (($h) && (dns_get_record($h, DNS_A + DNS_CNAME + DNS_PTR + DNS_MX) || filter_var($h, FILTER_VALIDATE_IP) )) {
504                 return true;
505         }
506         return false;
507 }
508
509 /**
510  * @brief Check if URL is allowed
511  *
512  * Check $url against our list of allowed sites,
513  * wildcards allowed. If allowed_sites is unset return true;
514  *
515  * @param string $url URL which get tested
516  * @return boolean True if url is allowed otherwise return false
517  */
518 function allowed_url($url) {
519
520         $h = @parse_url($url);
521
522         if (! $h) {
523                 return false;
524         }
525
526         $str_allowed = Config::get('system', 'allowed_sites');
527         if (! $str_allowed) {
528                 return true;
529         }
530
531         $found = false;
532
533         $host = strtolower($h['host']);
534
535         // always allow our own site
536         if ($host == strtolower($_SERVER['SERVER_NAME'])) {
537                 return true;
538         }
539
540         $fnmatch = function_exists('fnmatch');
541         $allowed = explode(',', $str_allowed);
542
543         if (count($allowed)) {
544                 foreach ($allowed as $a) {
545                         $pat = strtolower(trim($a));
546                         if (($fnmatch && fnmatch($pat, $host)) || ($pat == $host)) {
547                                 $found = true;
548                                 break;
549                         }
550                 }
551         }
552         return $found;
553 }
554
555 /**
556  * Checks if the provided url domain is on the domain blocklist.
557  * Returns true if it is or malformed URL, false if not.
558  *
559  * @param string $url The url to check the domain from
560  * @return boolean
561  */
562 function blocked_url($url) {
563         $h = @parse_url($url);
564
565         if (! $h) {
566                 return true;
567         }
568
569         $domain_blocklist = Config::get('system', 'blocklist', array());
570         if (! $domain_blocklist) {
571                 return false;
572         }
573
574         $host = strtolower($h['host']);
575
576         foreach ($domain_blocklist as $domain_block) {
577                 if (strtolower($domain_block['domain']) == $host) {
578                         return true;
579                 }
580         }
581
582         return false;
583 }
584
585 /**
586  * @brief Check if email address is allowed to register here.
587  *
588  * Compare against our list (wildcards allowed).
589  *
590  * @param type $email
591  * @return boolean False if not allowed, true if allowed
592  *    or if allowed list is not configured
593  */
594 function allowed_email($email) {
595
596         $domain = strtolower(substr($email,strpos($email,'@') + 1));
597         if (! $domain) {
598                 return false;
599         }
600
601         $str_allowed = Config::get('system','allowed_email');
602         if (! $str_allowed) {
603                 return true;
604         }
605
606         $found = false;
607
608         $fnmatch = function_exists('fnmatch');
609         $allowed = explode(',',$str_allowed);
610
611         if (count($allowed)) {
612                 foreach ($allowed as $a) {
613                         $pat = strtolower(trim($a));
614                         if (($fnmatch && fnmatch($pat,$domain)) || ($pat == $domain)) {
615                                 $found = true;
616                                 break;
617                         }
618                 }
619         }
620         return $found;
621 }
622
623 function avatar_img($email) {
624
625         $avatar['size'] = 175;
626         $avatar['email'] = $email;
627         $avatar['url'] = '';
628         $avatar['success'] = false;
629
630         call_hooks('avatar_lookup', $avatar);
631
632         if (! $avatar['success']) {
633                 $avatar['url'] = System::baseUrl() . '/images/person-175.jpg';
634         }
635
636         logger('Avatar: ' . $avatar['email'] . ' ' . $avatar['url'], LOGGER_DEBUG);
637         return $avatar['url'];
638 }
639
640
641 function parse_xml_string($s, $strict = true) {
642         // the "strict" parameter is deactivated
643
644         /// @todo Move this function to the xml class
645         libxml_use_internal_errors(true);
646
647         $x = @simplexml_load_string($s);
648         if (!$x) {
649                 logger('libxml: parse: error: ' . $s, LOGGER_DATA);
650                 foreach (libxml_get_errors() as $err) {
651                         logger('libxml: parse: ' . $err->code." at ".$err->line.":".$err->column." : ".$err->message, LOGGER_DATA);
652                 }
653                 libxml_clear_errors();
654         }
655         return $x;
656 }
657
658 function scale_external_images($srctext, $include_link = true, $scale_replace = false) {
659
660         // Suppress "view full size"
661         if (intval(Config::get('system','no_view_full_size'))) {
662                 $include_link = false;
663         }
664
665         $a = get_app();
666
667         // Picture addresses can contain special characters
668         $s = htmlspecialchars_decode($srctext);
669
670         $matches = null;
671         $c = preg_match_all('/\[img.*?\](.*?)\[\/img\]/ism',$s,$matches,PREG_SET_ORDER);
672         if ($c) {
673                 require_once('include/Photo.php');
674                 foreach ($matches as $mtch) {
675                         logger('scale_external_image: ' . $mtch[1]);
676
677                         $hostname = str_replace('www.','',substr(System::baseUrl(),strpos(System::baseUrl(),'://')+3));
678                         if (stristr($mtch[1],$hostname)) {
679                                 continue;
680                         }
681
682                         // $scale_replace, if passed, is an array of two elements. The
683                         // first is the name of the full-size image. The second is the
684                         // name of a remote, scaled-down version of the full size image.
685                         // This allows Friendica to display the smaller remote image if
686                         // one exists, while still linking to the full-size image
687                         if ($scale_replace) {
688                                 $scaled = str_replace($scale_replace[0], $scale_replace[1], $mtch[1]);
689                         } else {
690                                 $scaled = $mtch[1];
691                         }
692                         $i = fetch_url($scaled);
693                         if (! $i) {
694                                 return $srctext;
695                         }
696
697                         // guess mimetype from headers or filename
698                         $type = guess_image_type($mtch[1],true);
699
700                         if ($i) {
701                                 $ph = new Photo($i, $type);
702                                 if ($ph->is_valid()) {
703                                         $orig_width = $ph->getWidth();
704                                         $orig_height = $ph->getHeight();
705
706                                         if ($orig_width > 640 || $orig_height > 640) {
707
708                                                 $ph->scaleImage(640);
709                                                 $new_width = $ph->getWidth();
710                                                 $new_height = $ph->getHeight();
711                                                 logger('scale_external_images: ' . $orig_width . '->' . $new_width . 'w ' . $orig_height . '->' . $new_height . 'h' . ' match: ' . $mtch[0], LOGGER_DEBUG);
712                                                 $s = str_replace($mtch[0],'[img=' . $new_width . 'x' . $new_height. ']' . $scaled . '[/img]'
713                                                         . "\n" . (($include_link)
714                                                                 ? '[url=' . $mtch[1] . ']' . t('view full size') . '[/url]' . "\n"
715                                                                 : ''),$s);
716                                                 logger('scale_external_images: new string: ' . $s, LOGGER_DEBUG);
717                                         }
718                                 }
719                         }
720                 }
721         }
722
723         // replace the special char encoding
724         $s = htmlspecialchars($s,ENT_NOQUOTES,'UTF-8');
725         return $s;
726 }
727
728
729 function fix_contact_ssl_policy(&$contact,$new_policy) {
730
731         $ssl_changed = false;
732         if ((intval($new_policy) == SSL_POLICY_SELFSIGN || $new_policy === 'self') && strstr($contact['url'],'https:')) {
733                 $ssl_changed = true;
734                 $contact['url']     =   str_replace('https:','http:',$contact['url']);
735                 $contact['request'] =   str_replace('https:','http:',$contact['request']);
736                 $contact['notify']  =   str_replace('https:','http:',$contact['notify']);
737                 $contact['poll']    =   str_replace('https:','http:',$contact['poll']);
738                 $contact['confirm'] =   str_replace('https:','http:',$contact['confirm']);
739                 $contact['poco']    =   str_replace('https:','http:',$contact['poco']);
740         }
741
742         if ((intval($new_policy) == SSL_POLICY_FULL || $new_policy === 'full') && strstr($contact['url'],'http:')) {
743                 $ssl_changed = true;
744                 $contact['url']     =   str_replace('http:','https:',$contact['url']);
745                 $contact['request'] =   str_replace('http:','https:',$contact['request']);
746                 $contact['notify']  =   str_replace('http:','https:',$contact['notify']);
747                 $contact['poll']    =   str_replace('http:','https:',$contact['poll']);
748                 $contact['confirm'] =   str_replace('http:','https:',$contact['confirm']);
749                 $contact['poco']    =   str_replace('http:','https:',$contact['poco']);
750         }
751
752         if ($ssl_changed) {
753                 $fields = array('url' => $contact['url'], 'request' => $contact['request'],
754                                 'notify' => $contact['notify'], 'poll' => $contact['poll'],
755                                 'confirm' => $contact['confirm'], 'poco' => $contact['poco']);
756                 dba::update('contact', $fields, array('id' => $contact['id']));
757         }
758 }
759
760 /**
761  * @brief Remove Google Analytics and other tracking platforms params from URL
762  *
763  * @param string $url Any user-submitted URL that may contain tracking params
764  * @return string The same URL stripped of tracking parameters
765  */
766 function strip_tracking_query_params($url)
767 {
768         $urldata = parse_url($url);
769         if (is_string($urldata["query"])) {
770                 $query = $urldata["query"];
771                 parse_str($query, $querydata);
772
773                 if (is_array($querydata)) {
774                         foreach ($querydata AS $param => $value) {
775                                 if (in_array($param, array("utm_source", "utm_medium", "utm_term", "utm_content", "utm_campaign",
776                                                         "wt_mc", "pk_campaign", "pk_kwd", "mc_cid", "mc_eid",
777                                                         "fb_action_ids", "fb_action_types", "fb_ref",
778                                                         "awesm", "wtrid",
779                                                         "woo_campaign", "woo_source", "woo_medium", "woo_content", "woo_term"))) {
780
781                                         $pair = $param . "=" . urlencode($value);
782                                         $url = str_replace($pair, "", $url);
783
784                                         // Second try: if the url isn't encoded completely
785                                         $pair = $param . "=" . str_replace(" ", "+", $value);
786                                         $url = str_replace($pair, "", $url);
787
788                                         // Third try: Maybey the url isn't encoded at all
789                                         $pair = $param . "=" . $value;
790                                         $url = str_replace($pair, "", $url);
791
792                                         $url = str_replace(array("?&", "&&"), array("?", ""), $url);
793                                 }
794                         }
795                 }
796
797                 if (substr($url, -1, 1) == "?") {
798                         $url = substr($url, 0, -1);
799                 }
800         }
801
802         return $url;
803 }
804
805 /**
806  * @brief Returns the original URL of the provided URL
807  *
808  * This function strips tracking query params and follows redirections, either
809  * through HTTP code or meta refresh tags. Stops after 10 redirections.
810  *
811  * @todo Remove the $fetchbody parameter that generates an extraneous HEAD request
812  *
813  * @see ParseUrl::getSiteinfo
814  *
815  * @param string $url A user-submitted URL
816  * @param int $depth The current redirection recursion level (internal)
817  * @param bool $fetchbody Wether to fetch the body or not after the HEAD requests
818  * @return string A canonical URL
819  */
820 function original_url($url, $depth = 1, $fetchbody = false) {
821         $a = get_app();
822
823         $url = strip_tracking_query_params($url);
824
825         if ($depth > 10)
826                 return($url);
827
828         $url = trim($url, "'");
829
830         $stamp1 = microtime(true);
831
832         $siteinfo = array();
833         $ch = curl_init();
834         curl_setopt($ch, CURLOPT_URL, $url);
835         curl_setopt($ch, CURLOPT_HEADER, 1);
836         curl_setopt($ch, CURLOPT_NOBODY, 1);
837         curl_setopt($ch, CURLOPT_TIMEOUT, 10);
838         curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
839         curl_setopt($ch, CURLOPT_USERAGENT, $a->get_useragent());
840
841         $header = curl_exec($ch);
842         $curl_info = @curl_getinfo($ch);
843         $http_code = $curl_info['http_code'];
844         curl_close($ch);
845
846         $a->save_timestamp($stamp1, "network");
847
848         if ($http_code == 0)
849                 return($url);
850
851         if ((($curl_info['http_code'] == "301") || ($curl_info['http_code'] == "302"))
852                 && (($curl_info['redirect_url'] != "") || ($curl_info['location'] != ""))) {
853                 if ($curl_info['redirect_url'] != "")
854                         return(original_url($curl_info['redirect_url'], ++$depth, $fetchbody));
855                 else
856                         return(original_url($curl_info['location'], ++$depth, $fetchbody));
857         }
858
859         // Check for redirects in the meta elements of the body if there are no redirects in the header.
860         if (!$fetchbody)
861                 return(original_url($url, ++$depth, true));
862
863         // if the file is too large then exit
864         if ($curl_info["download_content_length"] > 1000000)
865                 return($url);
866
867         // if it isn't a HTML file then exit
868         if (($curl_info["content_type"] != "") && !strstr(strtolower($curl_info["content_type"]),"html"))
869                 return($url);
870
871         $stamp1 = microtime(true);
872
873         $ch = curl_init();
874         curl_setopt($ch, CURLOPT_URL, $url);
875         curl_setopt($ch, CURLOPT_HEADER, 0);
876         curl_setopt($ch, CURLOPT_NOBODY, 0);
877         curl_setopt($ch, CURLOPT_TIMEOUT, 10);
878         curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
879         curl_setopt($ch, CURLOPT_USERAGENT, $a->get_useragent());
880
881         $body = curl_exec($ch);
882         curl_close($ch);
883
884         $a->save_timestamp($stamp1, "network");
885
886         if (trim($body) == "")
887                 return($url);
888
889         // Check for redirect in meta elements
890         $doc = new DOMDocument();
891         @$doc->loadHTML($body);
892
893         $xpath = new DomXPath($doc);
894
895         $list = $xpath->query("//meta[@content]");
896         foreach ($list as $node) {
897                 $attr = array();
898                 if ($node->attributes->length)
899                         foreach ($node->attributes as $attribute)
900                                 $attr[$attribute->name] = $attribute->value;
901
902                 if (@$attr["http-equiv"] == 'refresh') {
903                         $path = $attr["content"];
904                         $pathinfo = explode(";", $path);
905                         $content = "";
906                         foreach ($pathinfo AS $value)
907                                 if (substr(strtolower($value), 0, 4) == "url=")
908                                         return(original_url(substr($value, 4), ++$depth));
909                 }
910         }
911
912         return($url);
913 }
914
915 function short_link($url) {
916         require_once('library/slinky.php');
917         $slinky = new Slinky($url);
918         $yourls_url = Config::get('yourls','url1');
919         if ($yourls_url) {
920                 $yourls_username = Config::get('yourls','username1');
921                 $yourls_password = Config::get('yourls', 'password1');
922                 $yourls_ssl = Config::get('yourls', 'ssl1');
923                 $yourls = new Slinky_YourLS();
924                 $yourls->set('username', $yourls_username);
925                 $yourls->set('password', $yourls_password);
926                 $yourls->set('ssl', $yourls_ssl);
927                 $yourls->set('yourls-url', $yourls_url);
928                 $slinky->set_cascade(array($yourls, new Slinky_Ur1ca(), new Slinky_TinyURL()));
929         } else {
930                 // setup a cascade of shortening services
931                 // try to get a short link from these services
932                 // in the order ur1.ca, tinyurl
933                 $slinky->set_cascade(array(new Slinky_Ur1ca(), new Slinky_TinyURL()));
934         }
935         return $slinky->short();
936 }
937
938 /**
939  * @brief Encodes content to json
940  *
941  * This function encodes an array to json format
942  * and adds an application/json HTTP header to the output.
943  * After finishing the process is getting killed.
944  *
945  * @param array $x The input content
946  */
947 function json_return_and_die($x) {
948         header("content-type: application/json");
949         echo json_encode($x);
950         killme();
951 }
952
953 /**
954  * @brief Find the matching part between two url
955  *
956  * @param string $url1
957  * @param string $url2
958  * @return string The matching part
959  */
960 function matching_url($url1, $url2) {
961
962         if (($url1 == "") || ($url2 == ""))
963                 return "";
964
965         $url1 = normalise_link($url1);
966         $url2 = normalise_link($url2);
967
968         $parts1 = parse_url($url1);
969         $parts2 = parse_url($url2);
970
971         if (!isset($parts1["host"]) || !isset($parts2["host"]))
972                 return "";
973
974         if ($parts1["scheme"] != $parts2["scheme"])
975                 return "";
976
977         if ($parts1["host"] != $parts2["host"])
978                 return "";
979
980         if ($parts1["port"] != $parts2["port"])
981                 return "";
982
983         $match = $parts1["scheme"]."://".$parts1["host"];
984
985         if ($parts1["port"])
986                 $match .= ":".$parts1["port"];
987
988         $pathparts1 = explode("/", $parts1["path"]);
989         $pathparts2 = explode("/", $parts2["path"]);
990
991         $i = 0;
992         $path = "";
993         do {
994                 $path1 = $pathparts1[$i];
995                 $path2 = $pathparts2[$i];
996
997                 if ($path1 == $path2)
998                         $path .= $path1."/";
999
1000         } while (($path1 == $path2) && ($i++ <= count($pathparts1)));
1001
1002         $match .= $path;
1003
1004         return normalise_link($match);
1005 }
1006
1007 /**
1008  * @brief Glue url parts together
1009  *
1010  * @param array $parsed URL parts
1011  *
1012  * @return string The glued URL
1013  */
1014 function unParseUrl($parsed) {
1015         $get = function ($key) use ($parsed) {
1016                 return isset($parsed[$key]) ? $parsed[$key] : null;
1017         };
1018
1019         $pass      = $get('pass');
1020         $user      = $get('user');
1021         $userinfo  = $pass !== null ? "$user:$pass" : $user;
1022         $port      = $get('port');
1023         $scheme    = $get('scheme');
1024         $query     = $get('query');
1025         $fragment  = $get('fragment');
1026         $authority =
1027                 ($userinfo !== null ? $userinfo."@" : '') .
1028                 $get('host') .
1029                 ($port ? ":$port" : '');
1030
1031         return  (strlen($scheme) ? $scheme.":" : '') .
1032                 (strlen($authority) ? "//".$authority : '') .
1033                 $get('path') .
1034                 (strlen($query) ? "?".$query : '') .
1035                 (strlen($fragment) ? "#".$fragment : '');
1036 }