* 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 = '[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.
*/
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) {
+ if (mb_strlen($str) > self::MAX_LEN) {
+ // Display forms must also fit!
+ throw new NicknameTooLongException();
+ } elseif (mb_strlen($str) < 1) {
throw new NicknameEmptyException();
- }
- if (!self::isCanonical($str)) {
+ } elseif (!self::isCanonical($str)) {
throw new NicknameInvalidException();
- }
- if (self::isBlacklisted($str)) {
+ } elseif (self::isBlacklisted($str)) {
throw new NicknameBlacklistedException();
- }
- if (self::isSystemPath($str)) {
+ } elseif (self::isSystemPath($str)) {
throw new NicknamePathCollisionException();
- }
- if ($checkuse && $user = self::isTaken($str)) {
- if ($user instanceof User) {
- throw new NicknameTakenException();
+ } elseif ($checkuse) {
+ $profile = self::isTaken($str);
+ if ($profile instanceof Profile) {
+ throw new NicknameTakenException($profile);
}
}
// All directory and file names in site root should be blacklisted
$d = dir(INSTALLDIR);
while (false !== ($entry = $d->read())) {
- $paths[] = $entry;
+ $paths[$entry] = true;
}
$d->close();
// All top level names in the router should be blacklisted
$router = Router::get();
- foreach (array_keys($router->m->getPaths()) as $path) {
- if (preg_match('/^\/(.*?)[\/\?]/',$path,$matches)) {
- $paths[] = $matches[1];
+ foreach ($router->m->getPaths() as $path) {
+ if (preg_match('/^([^\/\?]+)[\/\?]/',$path,$matches) && isset($matches[1])) {
+ $paths[$matches[1]] = true;
}
}
- return in_array($str, $paths);
+
+ // 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 User|null Returns null if no such user, otherwise a User object
+ * @return Profile|null Returns Profile if nickname found, otherwise null
*/
public static function isTaken($str)
{
- $user = User::getKV('nickname', $str);
- return $user; // null if no such User entry
+ $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 NicknameTakenException extends NicknameException
{
- public $user = null; // the User which occupies the nickname
+ public $profile = null; // the Profile which occupies the nickname
- public function __construct(User $user, $msg=null, $code=400)
+ public function __construct(Profile $profile, $msg=null, $code=400)
{
- $this->byuser = $user;
+ $this->profile = $profile;
if ($msg === null) {
$msg = $this->defaultMessage();