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