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