]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - lib/nickname.php
80be6239c500decb6064920e21f342c924acd36d
[quix0rs-gnu-social.git] / lib / nickname.php
1 <?php
2 /*
3  * StatusNet - the distributed open-source microblogging tool
4  * Copyright (C) 2008, 2009, StatusNet, Inc.
5  *
6  * This program is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU Affero General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU Affero General Public License for more details.
15  *
16  * You should have received a copy of the GNU Affero General Public License
17  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19
20 class Nickname
21 {
22     /**
23      * Regex fragment for pulling a formated nickname *OR* ID number.
24      * Suitable for router def of 'id' parameters on API actions.
25      *
26      * Not guaranteed to be valid after normalization; run the string through
27      * Nickname::normalize() to get the canonical form, or Nickname::isValid()
28      * if you just need to check if it's properly formatted.
29      *
30      * This, DISPLAY_FMT, and CANONICAL_FMT should not be enclosed in []s.
31      *
32      * @fixme would prefer to define in reference to the other constants
33      */
34     const INPUT_FMT = '(?:[0-9]+|[0-9a-zA-Z_]{1,64})';
35
36     /**
37      * Regex fragment for acceptable user-formatted variant of a nickname.
38      *
39      * This includes some chars such as underscore which will be removed
40      * from the normalized canonical form, but still must fit within
41      * field length limits.
42      *
43      * Not guaranteed to be valid after normalization; run the string through
44      * Nickname::normalize() to get the canonical form, or Nickname::isValid()
45      * if you just need to check if it's properly formatted.
46      *
47      * This, INPUT_FMT and CANONICAL_FMT should not be enclosed in []s.
48      */
49     const DISPLAY_FMT = '[0-9a-zA-Z_]{1,64}';
50
51     /**
52      * Regex fragment for checking a canonical nickname.
53      *
54      * Any non-matching string is not a valid canonical/normalized nickname.
55      * Matching strings are valid and canonical form, but may still be
56      * unavailable for registration due to blacklisting et.
57      *
58      * Only the canonical forms should be stored as keys in the database;
59      * there are multiple possible denormalized forms for each valid
60      * canonical-form name.
61      *
62      * This, INPUT_FMT and DISPLAY_FMT should not be enclosed in []s.
63      */
64     const CANONICAL_FMT = '[0-9a-z]{1,64}';
65
66     /**
67      * Maximum number of characters in a canonical-form nickname.
68      */
69     const MAX_LEN = 64;
70
71     /**
72      * Nice simple check of whether the given string is a valid input nickname,
73      * which can be normalized into an internally canonical form.
74      *
75      * Note that valid nicknames may be in use or reserved.
76      *
77      * @param string    $str       The nickname string to test
78      * @param boolean   $checkuse  Check if it's in use (return false if it is)
79      *
80      * @return boolean  True if nickname is valid. False if invalid (or taken if checkuse==true).
81      */
82     public static function isValid($str, $checkuse=false)
83     {
84         try {
85             self::normalize($str, $checkuse);
86         } catch (NicknameException $e) {
87             return false;
88         }
89
90         return true;
91     }
92
93     /**
94      * Validate an input nickname string, and normalize it to its canonical form.
95      * The canonical form will be returned, or an exception thrown if invalid.
96      *
97      * @param string    $str       The nickname string to test
98      * @param boolean   $checkuse  Check if it's in use (return false if it is)
99      * @return string Normalized canonical form of $str
100      *
101      * @throws NicknameException (base class)
102      * @throws   NicknameBlacklistedException
103      * @throws   NicknameEmptyException
104      * @throws   NicknameInvalidException
105      * @throws   NicknamePathCollisionException
106      * @throws   NicknameTakenException
107      * @throws   NicknameTooLongException
108      */
109     public static function normalize($str, $checkuse=false)
110     {
111         // We should also have UTF-8 normalization (å to a etc.)
112         $str = trim($str);
113         $str = str_replace('_', '', $str);
114         $str = mb_strtolower($str);
115
116         if (mb_strlen($str) > self::MAX_LEN) {
117             // Display forms must also fit!
118             throw new NicknameTooLongException();
119         } elseif (mb_strlen($str) < 1) {
120             throw new NicknameEmptyException();
121         } elseif (!self::isCanonical($str)) {
122             throw new NicknameInvalidException();
123         } elseif (self::isBlacklisted($str)) {
124             throw new NicknameBlacklistedException();
125         } elseif (self::isSystemPath($str)) {
126             throw new NicknamePathCollisionException();
127         } elseif ($checkuse) {
128             $profile = self::isTaken($str);
129             if ($profile instanceof Profile) {
130                 throw new NicknameTakenException($profile);
131             }
132         }
133
134         return $str;
135     }
136
137     /**
138      * Is the given string a valid canonical nickname form?
139      *
140      * @param string $str
141      * @return boolean
142      */
143     public static function isCanonical($str)
144     {
145         return preg_match('/^(?:' . self::CANONICAL_FMT . ')$/', $str);
146     }
147
148     /**
149      * Is the given string in our nickname blacklist?
150      *
151      * @param string $str
152      * @return boolean
153      */
154      public static function isBlacklisted($str)
155      {
156          $blacklist = common_config('nickname', 'blacklist');
157          return in_array($str, $blacklist);
158      }
159
160     /**
161      * Is the given string identical to a system path or route?
162      * This could probably be put in some other class, but at
163      * at the moment, only Nickname requires this functionality.
164      *
165      * @param string $str
166      * @return boolean
167      */
168      public static function isSystemPath($str)
169      {
170         $paths = array();
171
172         // All directory and file names in site root should be blacklisted
173         $d = dir(INSTALLDIR);
174         while (false !== ($entry = $d->read())) {
175             $paths[] = $entry;
176         }
177         $d->close();
178
179         // All top level names in the router should be blacklisted
180         $router = Router::get();
181         foreach (array_keys($router->m->getPaths()) as $path) {
182             if (preg_match('/^\/(.*?)[\/\?]/',$path,$matches)) {
183                 $paths[] = $matches[1];
184             }
185         }
186         return in_array($str, $paths);
187     }
188
189     /**
190      * Is the nickname already in use locally? Checks the User table.
191      *
192      * @param   string $str
193      * @return  Profile|null   Returns Profile if nickname found, otherwise null
194      */
195     public static function isTaken($str)
196     {
197         $found = User::getKV('nickname', $str);
198         if ($found instanceof User) {
199             return $found->getProfile();
200         }
201
202         $found = Local_group::getKV('nickname', $str);
203         if ($found instanceof Local_group) {
204             return $found->getProfile();
205         }
206
207         $found = Group_alias::getKV('alias', $str);
208         if ($found instanceof Group_alias) {
209             return $found->getProfile();
210         }
211
212         return null;
213     }
214 }
215
216 class NicknameException extends ClientException
217 {
218     function __construct($msg=null, $code=400)
219     {
220         if ($msg === null) {
221             $msg = $this->defaultMessage();
222         }
223         parent::__construct($msg, $code);
224     }
225
226     /**
227      * Default localized message for this type of exception.
228      * @return string
229      */
230     protected function defaultMessage()
231     {
232         return null;
233     }
234 }
235
236 class NicknameInvalidException extends NicknameException {
237     /**
238      * Default localized message for this type of exception.
239      * @return string
240      */
241     protected function defaultMessage()
242     {
243         // TRANS: Validation error in form for registration, profile and group settings, etc.
244         return _('Nickname must have only lowercase letters and numbers and no spaces.');
245     }
246 }
247
248 class NicknameEmptyException extends NicknameInvalidException
249 {
250     /**
251      * Default localized message for this type of exception.
252      * @return string
253      */
254     protected function defaultMessage()
255     {
256         // TRANS: Validation error in form for registration, profile and group settings, etc.
257         return _('Nickname cannot be empty.');
258     }
259 }
260
261 class NicknameTooLongException extends NicknameInvalidException
262 {
263     /**
264      * Default localized message for this type of exception.
265      * @return string
266      */
267     protected function defaultMessage()
268     {
269         // TRANS: Validation error in form for registration, profile and group settings, etc.
270         return sprintf(_m('Nickname cannot be more than %d character long.',
271                           'Nickname cannot be more than %d characters long.',
272                           Nickname::MAX_LEN),
273                        Nickname::MAX_LEN);
274     }
275 }
276
277 class NicknameBlacklistedException extends NicknameException
278 {
279     protected function defaultMessage()
280     {
281         // TRANS: Validation error in form for registration, profile and group settings, etc.
282         return _('Nickname is disallowed through blacklist.');
283     }
284 }
285
286 class NicknamePathCollisionException extends NicknameException
287 {
288     protected function defaultMessage()
289     {
290         // TRANS: Validation error in form for registration, profile and group settings, etc.
291         return _('Nickname is identical to system path names.');
292     }
293 }
294
295 class NicknameTakenException extends NicknameException
296 {
297     public $profile = null;    // the Profile which occupies the nickname
298
299     public function __construct(Profile $profile, $msg=null, $code=400)
300     {
301         $this->profile = $profile;
302
303         if ($msg === null) {
304             $msg = $this->defaultMessage();
305         }
306
307         parent::__construct($msg, $code);
308     }
309
310     protected function defaultMessage()
311     {
312         // TRANS: Validation error in form for registration, profile and group settings, etc.
313         return _('Nickname is already in use on this server.');
314     }
315 }