* Nickname::normalize() to get the canonical form, or Nickname::isValid()
* if you just need to check if it's properly formatted.
*
- * This, DISPLAY_FMT, and CANONICAL_FMT replace the old NICKNAME_FMT,
- * but be aware that these should not be enclosed in []s.
+ * This, DISPLAY_FMT, and CANONICAL_FMT should not be enclosed in []s.
*
* @fixme would prefer to define in reference to the other constants
*/
/**
* Regex fragment for acceptable user-formatted variant of a nickname.
+ *
* This includes some chars such as underscore which will be removed
* from the normalized canonical form, but still must fit within
* field length limits.
* Nickname::normalize() to get the canonical form, or Nickname::isValid()
* if you just need to check if it's properly formatted.
*
- * This and CANONICAL_FMT replace the old NICKNAME_FMT, but be aware
- * that these should not be enclosed in []s.
+ * This, INPUT_FMT and CANONICAL_FMT should not be enclosed in []s.
*/
const DISPLAY_FMT = '[0-9a-zA-Z_]{1,64}';
+ /**
+ * Simplified regex fragment for acceptable full WebFinger ID of a user
+ *
+ * We could probably use an email regex here, but mainly we are interested
+ * in matching it in our URLs, like https://social.example/user@example.com
+ */
+ const WEBFINGER_FMT = '(?:\w+[\w\-\_\.]*)?\w+\@'.URL_REGEX_DOMAIN_NAME;
+
+ // old one without support for -_. in nickname part:
+ // const WEBFINGER_FMT = '[0-9a-zA-Z_]{1,64}\@[0-9a-zA-Z_-.]{3,255}';
+
/**
* Regex fragment for checking a canonical nickname.
*
* there are multiple possible denormalized forms for each valid
* canonical-form name.
*
- * This and DISPLAY_FMT replace the old NICKNAME_FMT, but be aware
- * that these should not be enclosed in []s.
+ * This, INPUT_FMT and DISPLAY_FMT should not be enclosed in []s.
*/
const CANONICAL_FMT = '[0-9a-z]{1,64}';
*/
const MAX_LEN = 64;
+ /**
+ * Regex with non-capturing group that matches whitespace and some
+ * characters which are allowed right before an @ or ! when mentioning
+ * other users. Like: 'This goes out to:@mmn (@chimo too) (!awwyiss).'
+ *
+ * FIXME: Make this so you can have multiple whitespace but not multiple
+ * parenthesis or something. '(((@n_n@)))' might as well be a smiley.
+ */
+ const BEFORE_MENTIONS = '(?:^|[\s\.\,\:\;\[\(]+)';
+
/**
* Nice simple check of whether the given string is a valid input nickname,
* which can be normalized into an internally canonical form.
*
* Note that valid nicknames may be in use or reserved.
*
- * @param string $str
- * @return boolean
+ * @param string $str The nickname string to test
+ * @param boolean $checkuse Check if it's in use (return false if it is)
+ *
+ * @return boolean True if nickname is valid. False if invalid (or taken if checkuse==true).
*/
- public static function isValid($str)
+ public static function isValid($str, $checkuse=false)
{
try {
- self::normalize($str);
- return true;
+ self::normalize($str, $checkuse);
} catch (NicknameException $e) {
return false;
}
+
+ return true;
}
/**
* Validate an input nickname string, and normalize it to its canonical form.
* The canonical form will be returned, or an exception thrown if invalid.
*
- * @param string $str
+ * @param string $str The nickname string to test
+ * @param boolean $checkuse Check if it's in use (return false if it is)
* @return string Normalized canonical form of $str
*
* @throws NicknameException (base class)
- * @throws NicknameInvalidException
+ * @throws NicknameBlacklistedException
* @throws NicknameEmptyException
+ * @throws NicknameInvalidException
+ * @throws NicknamePathCollisionException
+ * @throws NicknameTakenException
* @throws NicknameTooLongException
*/
- public static function normalize($str)
+ public static function normalize($str, $checkuse=false)
{
if (mb_strlen($str) > self::MAX_LEN) {
// Display forms must also fit!
throw new NicknameTooLongException();
}
+ // We should also have UTF-8 normalization (å to a etc.)
$str = trim($str);
$str = str_replace('_', '', $str);
$str = mb_strtolower($str);
if (mb_strlen($str) < 1) {
throw new NicknameEmptyException();
- }
- if (!self::isCanonical($str)) {
+ } elseif (!self::isCanonical($str)) {
throw new NicknameInvalidException();
- }
- if (!User::allowed_nickname($str)) {
- throw new NicknameBlacklistException();
+ } elseif (self::isBlacklisted($str)) {
+ throw new NicknameBlacklistedException();
+ } elseif (self::isSystemPath($str)) {
+ throw new NicknamePathCollisionException();
+ } elseif ($checkuse) {
+ $profile = self::isTaken($str);
+ if ($profile instanceof Profile) {
+ throw new NicknameTakenException($profile);
+ }
}
return $str;
{
return preg_match('/^(?:' . self::CANONICAL_FMT . ')$/', $str);
}
+
+ /**
+ * Is the given string in our nickname blacklist?
+ *
+ * @param string $str
+ * @return boolean
+ */
+ public static function isBlacklisted($str)
+ {
+ $blacklist = common_config('nickname', 'blacklist');
+ if(!$blacklist)
+ return false;
+ return in_array($str, $blacklist);
+ }
+
+ /**
+ * Is the given string identical to a system path or route?
+ * This could probably be put in some other class, but at
+ * at the moment, only Nickname requires this functionality.
+ *
+ * @param string $str
+ * @return boolean
+ */
+ public static function isSystemPath($str)
+ {
+ $paths = array();
+
+ // All directory and file names in site root should be blacklisted
+ $d = dir(INSTALLDIR);
+ while (false !== ($entry = $d->read())) {
+ $paths[$entry] = true;
+ }
+ $d->close();
+
+ // All top level names in the router should be blacklisted
+ $router = Router::get();
+ foreach ($router->m->getPaths() as $path) {
+ if (preg_match('/^([^\/\?]+)[\/\?]/',$path,$matches) && isset($matches[1])) {
+ $paths[$matches[1]] = true;
+ }
+ }
+
+ // FIXME: this assumes the 'path' is in the first-level directory, though common it's not certain
+ foreach (['avatar', 'attachments'] as $cat) {
+ $paths[basename(common_config($cat, 'path'))] = true;
+ }
+
+ return in_array($str, array_keys($paths));
+ }
+
+ /**
+ * Is the nickname already in use locally? Checks the User table.
+ *
+ * @param string $str
+ * @return Profile|null Returns Profile if nickname found, otherwise null
+ */
+ public static function isTaken($str)
+ {
+ $found = User::getKV('nickname', $str);
+ if ($found instanceof User) {
+ return $found->getProfile();
+ }
+
+ $found = Local_group::getKV('nickname', $str);
+ if ($found instanceof Local_group) {
+ return $found->getProfile();
+ }
+
+ $found = Group_alias::getKV('alias', $str);
+ if ($found instanceof Group_alias) {
+ return $found->getProfile();
+ }
+
+ return null;
+ }
}
class NicknameException extends ClientException
}
}
-class NicknameEmptyException extends NicknameException
+class NicknameEmptyException extends NicknameInvalidException
{
/**
* Default localized message for this type of exception.
}
}
-class NicknameBlacklistException extends NicknameInvalidException
+class NicknameBlacklistedException extends NicknameException
+{
+ protected function defaultMessage()
+ {
+ // TRANS: Validation error in form for registration, profile and group settings, etc.
+ return _('Nickname is disallowed through blacklist.');
+ }
+}
+
+class NicknamePathCollisionException extends NicknameException
+{
+ protected function defaultMessage()
+ {
+ // TRANS: Validation error in form for registration, profile and group settings, etc.
+ return _('Nickname is identical to system path names.');
+ }
+}
+
+class NicknameTakenException extends NicknameException
{
+ public $profile = null; // the Profile which occupies the nickname
+
+ public function __construct(Profile $profile, $msg=null, $code=400)
+ {
+ $this->profile = $profile;
+
+ if ($msg === null) {
+ $msg = $this->defaultMessage();
+ }
+
+ parent::__construct($msg, $code);
+ }
+
protected function defaultMessage()
{
// TRANS: Validation error in form for registration, profile and group settings, etc.
- return _('Nickname is disallowed through blacklist or system path list.');
+ return _('Nickname is already in use on this server.');
}
}