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