]> git.mxchange.org Git - quix0rs-gnu-social.git/blobdiff - lib/util.php
Merge branch 'avatar-folder-perms' into 'nightly'
[quix0rs-gnu-social.git] / lib / util.php
index f61ecf35d791a48b811615bbb43468d8f816f3dd..a2415945f1fbc01712d109935e2343f847db43cc 100644 (file)
@@ -264,6 +264,12 @@ function common_logged_in()
     return (!is_null(common_current_user()));
 }
 
+function common_local_referer()
+{
+    return isset($_SERVER['HTTP_REFERER'])
+            && parse_url($_SERVER['HTTP_REFERER'], PHP_URL_HOST) === common_config('site', 'server');
+}
+
 function common_have_session()
 {
     return (0 != strcmp(session_id(), ''));
@@ -354,7 +360,7 @@ function common_set_cookie($key, $value, $expiration=0)
                      $expiration,
                      $cookiepath,
                      $server,
-                     common_config('site', 'ssl')=='always');
+                     GNUsocial::useHTTPS());
 }
 
 define('REMEMBERME', 'rememberme');
@@ -575,12 +581,29 @@ function common_canonical_email($email)
     return $email;
 }
 
-function common_purify($html)
+function common_purify($html, array $args=array())
 {
     require_once INSTALLDIR.'/extlib/HTMLPurifier/HTMLPurifier.auto.php';
 
     $cfg = HTMLPurifier_Config::createDefault();
+    /**
+     * rel values that should be avoided since they can be used to infer
+     * information about the _current_ page, not the h-entry:
+     *
+     *      directory, home, license, payment
+     *
+     * Source: http://microformats.org/wiki/rel
+     */
+    $cfg->set('Attr.AllowedRel', ['bookmark', 'enclosure', 'nofollow', 'tag', 'noreferrer']);
     $cfg->set('HTML.ForbiddenAttributes', array('style'));  // id, on* etc. are already filtered by default
+    $cfg->set('URI.AllowedSchemes', array_fill_keys(common_url_schemes(), true));
+    if (isset($args['URI.Base'])) {
+        $cfg->set('URI.Base', $args['URI.Base']);   // if null this is like unsetting it I presume
+        $cfg->set('URI.MakeAbsolute', !is_null($args['URI.Base']));   // if we have a URI base, convert relative URLs to absolute ones.
+    }
+    foreach (common_config('htmlpurifier') as $key=>$val) {
+        $cfg->set($key, $val);
+    }
 
     // Remove more elements than what the default filter removes, default in GNU social are remotely
     // linked resources such as img, video, audio
@@ -670,7 +693,7 @@ function common_linkify_mention(array $mention)
         $xs = new XMLStringer(false);
 
         $attrs = array('href' => $mention['url'],
-                       'class' => 'h-card '.$mention['type']);
+                       'class' => 'h-card u-url p-nickname '.$mention['type']);
 
         if (!empty($mention['title'])) {
             $attrs['title'] = $mention['title'];
@@ -793,7 +816,7 @@ function common_find_mentions($text, Profile $sender, Notice $parent=null)
 
         // @#tag => mention of all subscriptions tagged 'tag'
 
-        preg_match_all('/(?:^|[\s\.\,\:\;]+)@#([\pL\pN_\-\.]{1,64})/',
+        preg_match_all('/'.Nickname::BEFORE_MENTIONS.'@#([\pL\pN_\-\.]{1,64})/',
                        $text, $hmatches, PREG_OFFSET_CAPTURE);
         foreach ($hmatches[1] as $hmatch) {
             $tag = common_canonical_tag($hmatch[0]);
@@ -815,7 +838,7 @@ function common_find_mentions($text, Profile $sender, Notice $parent=null)
                                 'url' => $url);
         }
 
-        preg_match_all('/(?:^|[\s\.\,\:\;]+)!(' . Nickname::DISPLAY_FMT . ')/',
+        preg_match_all('/'.Nickname::BEFORE_MENTIONS.'!(' . Nickname::DISPLAY_FMT . ')/',
                        $text, $hmatches, PREG_OFFSET_CAPTURE);
         foreach ($hmatches[1] as $hmatch) {
             $nickname = Nickname::normalize($hmatch[0]);
@@ -859,7 +882,7 @@ function common_find_mentions_raw($text)
 
     $atmatches = array();
     // the regexp's "(?!\@)" makes sure it doesn't matches the single "@remote" in "@remote@server.com"
-    preg_match_all('/(?:^|\s+)@(' . Nickname::DISPLAY_FMT . ')\b(?!\@)/',
+    preg_match_all('/'.Nickname::BEFORE_MENTIONS.'@(' . Nickname::DISPLAY_FMT . ')\b(?!\@)/',
                    $text,
                    $atmatches,
                    PREG_OFFSET_CAPTURE);
@@ -884,6 +907,7 @@ function common_render_text($text)
 define('_URL_SCHEME_COLON_DOUBLE_SLASH', 1);
 define('_URL_SCHEME_SINGLE_COLON', 2);
 define('_URL_SCHEME_NO_DOMAIN', 4);
+define('_URL_SCHEME_COLON_COORDINATES', 8);
 
 function common_url_schemes($filter=null)
 {
@@ -913,6 +937,7 @@ function common_url_schemes($filter=null)
                 'tel'       => _URL_SCHEME_SINGLE_COLON,
                 'xmpp'      => _URL_SCHEME_SINGLE_COLON,
                 'magnet'    => _URL_SCHEME_NO_DOMAIN,
+                'geo'       => _URL_SCHEME_COLON_COORDINATES,
                 ];
 
     return array_keys(
@@ -931,6 +956,13 @@ function common_url_schemes($filter=null)
  * @param mixed $arg: optional argument will be passed on to the callback
  */
 function common_replace_urls_callback($text, $callback, $arg = null) {
+    $geouri_labeltext_regex = '\pN\pL\-';
+    $geouri_mark_regex = '\-\_\.\!\~\*\\\'\(\)';    // the \\\' is really pretty
+    $geouri_unreserved_regex = '\pN\pL' . $geouri_mark_regex;
+    $geouri_punreserved_regex = '\[\]\:\&\+\$';
+    $geouri_pctencoded_regex = '(?:\%[0-9a-fA-F][0-9a-fA-F])';
+    $geouri_paramchar_regex = $geouri_unreserved_regex . $geouri_punreserved_regex; //FIXME: add $geouri_pctencoded_regex here so it works
+
     // Start off with a regex
     $regex = '#'.
     '(?:^|[\s\<\>\(\)\[\]\{\}\\\'\\\";]+)(?![\@\!\#])'.
@@ -951,12 +983,26 @@ function common_replace_urls_callback($text, $callback, $arg = null) {
                     ')'.
                 ')'.
             ')'.
-            // URLs without domain name, like magnet:?xt=...
-            '|(?:(?:' . implode('|', common_url_schemes(_URL_SCHEME_NO_DOMAIN)) . '):)'.
-            '|(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)'. //IPv4
-            '|(?:'. //IPv6
-                '\[?(?:(?:(?:[0-9A-Fa-f]{1,4}:){7}(?:(?:[0-9A-Fa-f]{1,4})|:))|(?:(?:[0-9A-Fa-f]{1,4}:){6}(?::|(?:(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})(?:\.(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})|(?::[0-9A-Fa-f]{1,4})))|(?:(?:[0-9A-Fa-f]{1,4}:){5}(?:(?::(?:(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})(?:\.(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})?)|(?:(?::[0-9A-Fa-f]{1,4}){1,2})))|(?:(?:[0-9A-Fa-f]{1,4}:){4}(?::[0-9A-Fa-f]{1,4}){0,1}(?:(?::(?:(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})(?:\.(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})?)|(?:(?::[0-9A-Fa-f]{1,4}){1,2})))|(?:(?:[0-9A-Fa-f]{1,4}:){3}(?::[0-9A-Fa-f]{1,4}){0,2}(?:(?::(?:(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})(?:\.(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})?)|(?:(?::[0-9A-Fa-f]{1,4}){1,2})))|(?:(?:[0-9A-Fa-f]{1,4}:){2}(?::[0-9A-Fa-f]{1,4}){0,3}(?:(?::(?:(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})(?:\.(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})?)|(?:(?::[0-9A-Fa-f]{1,4}){1,2})))|(?:(?:[0-9A-Fa-f]{1,4}:)(?::[0-9A-Fa-f]{1,4}){0,4}(?:(?::(?:(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})(?:\.(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})?)|(?:(?::[0-9A-Fa-f]{1,4}){1,2})))|(?::(?::[0-9A-Fa-f]{1,4}){0,5}(?:(?::(?:(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})(?:\.(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})?)|(?:(?::[0-9A-Fa-f]{1,4}){1,2})))|(?:(?:(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})(?:\.(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})))\]?(?<!:)'.
+            '|(?:'.
+                '(?:' . implode('|', common_url_schemes(_URL_SCHEME_COLON_COORDINATES)) . '):'.
+                // There's an order that must be followed here too, if ;crs= is used, it must precede ;u=
+                // Also 'crsp' (;crs=$crsp) must match $geouri_labeltext_regex
+                // Also 'uval' (;u=$uval) must be a pnum: \-?[0-9]+
+                '(?:'.
+                    '(?:[0-9]+(?:\.[0-9]+)?(?:\,[0-9]+(?:\.[0-9]+)?){1,2})'.    // 1(.23)?(,4(.56)){1,2}
+                    '(?:\;(?:['.$geouri_labeltext_regex.']+)(?:\=['.$geouri_paramchar_regex.']+)*)*'.
+                ')'.
             ')'.
+            // URLs without domain name, like magnet:?xt=...
+            '|(?:(?:' . implode('|', common_url_schemes(_URL_SCHEME_NO_DOMAIN)) . '):(?=\?))'.  // zero-length lookahead requires ? after :
+            (common_config('linkify', 'bare_ipv4')   // Convert IPv4 addresses to hyperlinks
+                ? '|(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)'
+                : '').
+            (common_config('linkify', 'bare_ipv6')   // Convert IPv6 addresses to hyperlinks
+                ? '|(?:'. //IPv6
+                    '\[?(?:(?:(?:[0-9A-Fa-f]{1,4}:){7}(?:(?:[0-9A-Fa-f]{1,4})|:))|(?:(?:[0-9A-Fa-f]{1,4}:){6}(?::|(?:(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})(?:\.(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})|(?::[0-9A-Fa-f]{1,4})))|(?:(?:[0-9A-Fa-f]{1,4}:){5}(?:(?::(?:(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})(?:\.(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})?)|(?:(?::[0-9A-Fa-f]{1,4}){1,2})))|(?:(?:[0-9A-Fa-f]{1,4}:){4}(?::[0-9A-Fa-f]{1,4}){0,1}(?:(?::(?:(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})(?:\.(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})?)|(?:(?::[0-9A-Fa-f]{1,4}){1,2})))|(?:(?:[0-9A-Fa-f]{1,4}:){3}(?::[0-9A-Fa-f]{1,4}){0,2}(?:(?::(?:(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})(?:\.(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})?)|(?:(?::[0-9A-Fa-f]{1,4}){1,2})))|(?:(?:[0-9A-Fa-f]{1,4}:){2}(?::[0-9A-Fa-f]{1,4}){0,3}(?:(?::(?:(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})(?:\.(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})?)|(?:(?::[0-9A-Fa-f]{1,4}){1,2})))|(?:(?:[0-9A-Fa-f]{1,4}:)(?::[0-9A-Fa-f]{1,4}){0,4}(?:(?::(?:(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})(?:\.(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})?)|(?:(?::[0-9A-Fa-f]{1,4}){1,2})))|(?::(?::[0-9A-Fa-f]{1,4}){0,5}(?:(?::(?:(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})(?:\.(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})?)|(?:(?::[0-9A-Fa-f]{1,4}){1,2})))|(?:(?:(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})(?:\.(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})))\]?(?<!:)'.
+                    ')'
+                : '').
             (common_config('linkify', 'bare_domains')
                 ? '|(?:'. //DNS
                     '(?:[\pN\pL\-\_\+\%\~]+(?:\:[\pN\pL\-\_\+\%\~]+)?\@)?'. //user:pass@
@@ -968,10 +1014,10 @@ function common_replace_urls_callback($text, $callback, $arg = null) {
         ')'.
         '(?:'.
             '(?:\:\d+)?'. //:port
-            '(?:/[\pN\pL$\,\!\(\)\.\:\-\_\+\/\=\&\;\%\~\*\$\+\'@]*)?'. // /path
-            '(?:\?[\pN\pL\$\,\!\(\)\.\:\-\_\+\/\=\&\;\%\~\*\$\+\'@\/]*)?'. // ?query string
-            '(?:\#[\pN\pL$\,\!\(\)\.\:\-\_\+\/\=\&\;\%\~\*\$\+\'\@/\?\#]*)?'. // #fragment
-        ')(?<![\?\.\,\#\,])'.
+            '(?:/['  . URL_REGEX_VALID_PATH_CHARS    . ']*)?'.  // path
+            '(?:\?[' . URL_REGEX_VALID_QSTRING_CHARS . ']*)?'.  // ?query string
+            '(?:\#[' . URL_REGEX_VALID_FRAGMENT_CHARS . ']*)?'. // #fragment
+        ')(?<!['. URL_REGEX_EXCLUDED_END_CHARS .'])'.
     ')'.
     '#ixu';
     //preg_match_all($regex,$text,$matches);
@@ -1094,17 +1140,7 @@ function common_linkify($url) {
         }
     }
 
-    // Add clippy
-    if ($is_attachment) {
-        $attrs['class'] = 'attachment';
-        if ($has_thumb) {
-            $attrs['class'] = 'attachment thumbnail';
-        }
-        $attrs['id'] = "attachment-{$attachment_id}";
-    }
-
     // Whether to nofollow
-
     $nf = common_config('nofollow', 'external');
 
     if ($nf == 'never') {
@@ -1113,6 +1149,16 @@ function common_linkify($url) {
         $attrs['rel'] = 'nofollow external';
     }
 
+    // Add clippy
+    if ($is_attachment) {
+        $attrs['class'] = 'attachment';
+        if ($has_thumb) {
+            $attrs['class'] = 'attachment thumbnail';
+        }
+        $attrs['id'] = "attachment-{$attachment_id}";
+        $attrs['rel'] .= ' noreferrer';
+    }
+
     return XMLStringer::estring('a', $attrs, $url);
 }
 
@@ -1320,9 +1366,7 @@ function common_local_url($action, $args=null, $params=null, $fragment=null, $ad
         $r = Router::get();
         $path = $r->build($action, $args, $params, $fragment);
 
-        $ssl = common_config('site', 'ssl') === 'always'
-                || GNUsocial::isHTTPS()
-                || common_is_sensitive($action);
+        $ssl = GNUsocial::useHTTPS();
 
         if (common_config('site','fancy')) {
             $url = common_path($path, $ssl, $addSession);
@@ -1338,35 +1382,11 @@ function common_local_url($action, $args=null, $params=null, $fragment=null, $ad
     return $url;
 }
 
-function common_is_sensitive($action)
-{
-    static $sensitive = array(
-        'login',
-        'register',
-        'passwordsettings',
-        'api',
-        'ApiOAuthRequestToken',
-        'ApiOAuthAccessToken',
-        'ApiOAuthAuthorize',
-        'ApiOAuthPin',
-        'showapplication'
-    );
-    $ssl = null;
-
-    if (Event::handle('SensitiveAction', array($action, &$ssl))) {
-        $ssl = in_array($action, $sensitive);
-    }
-
-    return $ssl;
-}
-
 function common_path($relative, $ssl=false, $addSession=true)
 {
     $pathpart = (common_config('site', 'path')) ? common_config('site', 'path')."/" : '';
 
-    if (($ssl && (common_config('site', 'ssl') === 'sometimes'))
-        || GNUsocial::isHTTPS()
-        || common_config('site', 'ssl') === 'always') {
+    if ($ssl && GNUsocial::useHTTPS()) {
         $proto = 'https';
         if (is_string(common_config('site', 'sslserver')) &&
             mb_strlen(common_config('site', 'sslserver')) > 0) {
@@ -1392,6 +1412,74 @@ function common_path($relative, $ssl=false, $addSession=true)
     return $proto.'://'.$serverpart.'/'.$pathpart.$relative;
 }
 
+// FIXME: Maybe this should also be able to handle non-fancy URLs with index.php?p=...
+function common_fake_local_fancy_url($url)
+{
+    /**
+     * This is a hacky fix to make URIs generated with "index.php/" match against
+     * locally stored URIs without that. So for example if the remote site is looking
+     * up the webfinger for some user and for some reason knows about https://some.example/user/1
+     * but we locally store and report only https://some.example/index.php/user/1 then they would
+     * dismiss the profile for not having an identified alias.
+     *
+     * There are various live instances where these issues occur, for various reasons.
+     * Most of them being users fiddling with configuration while already having
+     * started federating (distributing the URI to other servers) or maybe manually
+     * editing the local database.
+     */
+    if (!preg_match(
+                // [1] protocol part, we can only rewrite http/https anyway.
+                '/^(https?:\/\/)' .
+                // [2] site name.
+                // FIXME: Dunno how this acts if we're aliasing ourselves with a .onion domain etc.
+                '('.preg_quote(common_config('site', 'server'), '/').')' .
+                // [3] site path, or if that is empty just '/' (to retain the /)
+                '('.preg_quote(common_config('site', 'path') ?: '/', '/').')' .
+                // [4] + [5] extract index.php (+ possible leading double /) and the rest of the URL separately.
+                '(\/?index\.php\/)(.*)$/', $url, $matches)) {
+        // if preg_match failed to match
+        throw new Exception('No known change could be made to the URL.');
+    }
+
+    // now reconstruct the URL with everything except the "index.php/" part
+    $fancy_url = '';
+    foreach ([1,2,3,5] as $idx) {
+        $fancy_url .= $matches[$idx];
+    }
+    return $fancy_url;
+}
+
+// FIXME: Maybe this should also be able to handle non-fancy URLs with index.php?p=...
+function common_fake_local_nonfancy_url($url)
+{
+    /**
+     * This is a hacky fix to make URIs NOT generated with "index.php/" match against
+     * locally stored URIs WITH that. The reverse from the above.
+     *
+     * It will also "repair" index.php URLs with multiple / prepended. Like https://some.example///index.php/user/1
+     */
+    if (!preg_match(
+                // [1] protocol part, we can only rewrite http/https anyway.
+                '/^(https?:\/\/)' .
+                // [2] site name.
+                // FIXME: Dunno how this acts if we're aliasing ourselves with a .onion domain etc.
+                '('.preg_quote(common_config('site', 'server'), '/').')' .
+                // [3] site path, or if that is empty just '/' (to retain the /)
+                '('.preg_quote(common_config('site', 'path') ?: '/', '/').')' .
+                // [4] should be empty (might contain one or more / and then maybe also index.php). Will be overwritten.
+                // [5] will have the extracted actual URL part (besides site path)
+                '((?!index.php\/)\/*(?:index.php\/)?)(.*)$/', $url, $matches)) {
+        // if preg_match failed to match
+        throw new Exception('No known change could be made to the URL.');
+    }
+
+    $matches[4] = 'index.php/'; // inject the index.php/ rewritethingy
+
+    // remove the first element, which is the full matching string
+    array_shift($matches);
+    return implode($matches);
+}
+
 function common_inject_session($url, $serverpart = null)
 {
     if (!common_have_session()) {
@@ -1602,10 +1690,15 @@ function common_profile_url($nickname)
 
 /**
  * Should make up a reasonable root URL
+ *
+ * @param   bool    $tls    true or false to force TLS scheme, null to use server configuration
  */
-function common_root_url($ssl=false)
+function common_root_url($tls=null)
 {
-    $url = common_path('', $ssl, false);
+    if (is_null($tls)) {
+        $tls = GNUsocial::useHTTPS();
+    }
+    $url = common_path('', $tls, false);
     $i = strpos($url, '?');
     if ($i !== false) {
         $url = substr($url, 0, $i);