Issues (173)

Security Analysis    13 potential vulnerabilities

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  Response Splitting (3)
Response Splitting can be used to send arbitrary responses.
  File Manipulation (6)
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Cross-Site Scripting (1)
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

web/lib/common/InputValidation.php (2 issues)

1
<?php
2
3
/*
4
 * *****************************************************************************
5
 * Contributions to this work were made on behalf of the GÉANT project, a 
6
 * project that has received funding from the European Union’s Framework 
7
 * Programme 7 under Grant Agreements No. 238875 (GN3) and No. 605243 (GN3plus),
8
 * Horizon 2020 research and innovation programme under Grant Agreements No. 
9
 * 691567 (GN4-1) and No. 731122 (GN4-2).
10
 * On behalf of the aforementioned projects, GEANT Association is the sole owner
11
 * of the copyright in all material which was developed by a member of the GÉANT
12
 * project. GÉANT Vereniging (Association) is registered with the Chamber of 
13
 * Commerce in Amsterdam with registration number 40535155 and operates in the 
14
 * UK as a branch of GÉANT Vereniging.
15
 * 
16
 * Registered office: Hoekenrode 3, 1102BR Amsterdam, The Netherlands. 
17
 * UK branch address: City House, 126-130 Hills Road, Cambridge CB2 1PQ, UK
18
 *
19
 * License: see the web/copyright.inc.php file in the file structure or
20
 *          <base_url>/copyright.php after deploying the software
21
 */
22
23
namespace web\lib\common;
24
25
use \Exception;
0 ignored issues
show
The type \Exception was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
26
27
/**
28
 * performs validation of user inputs
29
 */
30
class InputValidation extends \core\common\Entity
31
{
32
33
    /**
34
     * returns a simple HTML <p> element with basic explanations about what was
35
     * wrong with the input
36
     * 
37
     * @param string $customtext explanation provided by the validator function
38
     * @return string
39
     */
40
    private function inputValidationError($customtext)
41
    {
42
        \core\common\Entity::intoThePotatoes();
43
        $retval = "<p>" . _("Input validation error: ") . $customtext . "</p>";
44
        \core\common\Entity::outOfThePotatoes();
45
        return $retval;
46
    }
47
48
    /**
49
     * Is this a known Federation? Optionally, also check if the authenticated
50
     * user is a federation admin of that federation
51
     * @param mixed       $input the ISO code of the federation
52
     * @param string|NULL $owner the authenticated username, optional
53
     * @return \core\Federation
54
     * @throws Exception
55
     */
56
    public function existingFederation($input, $owner = NULL)
57
    {
58
        $cat = new \core\CAT(); // initialises Entity static members
59
        $fedIdentifiers = array_keys($cat->knownFederations);
60
        if (!in_array(strtoupper($input), $fedIdentifiers)) {
61
            throw new Exception($this->inputValidationError(sprintf("This %s does not exist!", \core\common\Entity::$nomenclature_fed)));
62
        }
63
        // totally circular, but this hopefully *finally* make Scrutinizer happier
64
        $correctIndex = array_search(strtoupper($input), $fedIdentifiers);
65
        $postFed = $fedIdentifiers[$correctIndex];
66
67
        $temp = new \core\Federation($postFed);
68
        if ($owner === NULL) {
69
            return $temp;
70
        }
71
72
        foreach ($temp->listFederationAdmins() as $oneowner) {
73
            if ($oneowner == $owner) {
74
                return $temp;
75
            }
76
        }
77
        throw new Exception($this->inputValidationError(sprintf("User is not %s administrator!", \core\common\Entity::$nomenclature_fed)));
78
    }
79
80
    /**
81
     * Is this a known Federation? Optionally, also check if the authenticated
82
     * user is a federation admin of that federation
83
     * @param mixed       $input the ISO code of the federation
84
     * @param string|NULL $owner the authenticated username, optional
85
     * @return array(\core\Federation, string)
86
     * @throws Exception
87
     */
88
    public function existingFederationInt($input, $owner = NULL)
89
    {
90
        $cat = new \core\CAT(); // initialises Entity static members
91
        $fedIdentifiers = array_keys($cat->knownFederations);
92
        if (!in_array(strtoupper($input), $fedIdentifiers)) {
93
            throw new Exception($this->inputValidationError(sprintf("This %s does not exist!", \core\common\Entity::$nomenclature_fed)));
94
        }
95
        // totally circular, but this hopefully *finally* make Scrutinizer happier
96
        $correctIndex = array_search(strtoupper($input), $fedIdentifiers);
97
        $postFed = $fedIdentifiers[$correctIndex];
98
        $temp = new \core\Federation($postFed);
99
        if ($owner === NULL) {
100
            return [$temp,'readonly'];
101
        }
102
        $user = new \core\User($owner);        
103
        foreach ($temp->listFederationAdmins() as $oneowner) {
104
            if ($oneowner == $owner) {
105
                return [$temp, 'fullaccess'];
106
            }
107
        }
108
        if ($user->isSuperadmin()|| $user->isSupport()) {
109
                $this->loggerInstance->debug(4, "You are the superadmin/support\n");
110
                return [$temp,'readonly'];                
111
            }
112
        throw new Exception($this->inputValidationError(sprintf("User is not %s administrator!", \core\common\Entity::$nomenclature_fed)));
113
    }
114
    
115
    /**
116
     * Is this a known IdP? Optionally, also check if the authenticated
117
     * user is an admin of that IdP
118
     * It is a wrapper around existingIdPInt.
119
     * 
120
     * @param mixed            $input             the numeric ID of the IdP in the system
121
     * @param string           $owner             the authenticated username, optional
122
     * @param \core\Federation $claimedFedBinding if set, cross-check that IdP belongs to specified federation (useful in admin API mode)
123
     * @return \core\IdP
124
     * @throws Exception
125
     */
126
    public function existingIdP($input, $owner = NULL, $claimedFedBinding = NULL)
127
    {
128
        $clean = $this->integer($input);
129
        if ($clean === FALSE) {
130
            throw new Exception($this->inputValidationError("Value for IdP is not an integer!"));
131
        }
132
        
133
        $checkResult = $this->existingIdPInt($input, $owner, $claimedFedBinding);
134
        $this->loggerInstance->debug(5, $checkResult, "existingIdP:", "\n");
135
        if ($checkResult[1] == 'fullaccess') {
136
            return $checkResult[0];
137
        }
138
        if ($owner == NULL && $checkResult[1] == 'nouser') {
139
            return $checkResult[0];
140
        }
141
        return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type core\IdP.
Loading history...
142
    }
143
    
144
    
145
    /**
146
     * Is this a known IdP? Optionally, also check if the authenticated
147
     * user is an admin of that IdP or a federation admin for the parent federation
148
     * federaton admins superadmin and support get read-only access, superadmins get readonly access as well
149
     * @param mixed            $input             the numeric ID of the IdP in the system
150
     * @param string           $owner             the authenticated username, optional
151
     * @param \core\Federation $claimedFedBinding if set, cross-check that IdP belongs to specified federation (useful in admin API mode)
152
     * @return array(\core\IdP, string)
153
     * @throws Exception
154
     */
155
    public function existingIdPInt($input, $owner = NULL, $claimedFedBinding = NULL)
156
    {
157
        $clean = $this->integer($input);
158
        if ($clean === FALSE) {
159
            throw new Exception($this->inputValidationError("Value for IdP is not an integer!"));
160
        }
161
        $temp = new \core\IdP($input); // constructor throws an exception if NX, game over
162
        if ($owner !== NULL) { // check if the authenticated user is allowed to see this institution
163
            $user = new \core\User($owner);        
164
            foreach ($temp->listOwners() as $oneowner) {
165
                if ($oneowner['ID'] == $owner) {
166
                    return [$temp, 'fullaccess'];
167
                }
168
            }
169
            if ($user->isFederationAdmin($temp->federation)) {
170
                $this->loggerInstance->debug(4, "You are fed admin for this IdP\n");
171
                return [$temp,'readonly'];
172
            }
173
            if ($user->isSuperadmin() || $user->isSupport()) {
174
                $this->loggerInstance->debug(4, "You are the superadmin/support\n");
175
                return [$temp,'readonly'];                
176
            }
177
            throw new Exception($this->inputValidationError("This IdP identifier is not accessible!"));
178
        }
179
        if ($claimedFedBinding !== NULL && strtoupper($temp->federation) != strtoupper($claimedFedBinding->tld)) {
180
            throw new Exception($this->inputValidationError("This IdP does not belong to the claimed federation!"));
181
        }
182
        return [$temp,'nouser'];
183
    }
184
185
    /**
186
     * Checks if the input refers to a known Profile. Optionally also takes an
187
     * IdP identifier and then checks if the Profile belongs to the referenced 
188
     * IdP
189
     * 
190
     * @param mixed    $input         the numeric ID of the Profile in the system
191
     * @param int|NULL $idpIdentifier the numeric ID of the IdP in the system, optional
192
     * @return \core\AbstractProfile
193
     * @throws Exception
194
     */
195
    public function existingProfile($input, $idpIdentifier = NULL)
196
    {
197
        $clean = $this->integer($input);
198
        if ($clean === FALSE) {
199
            throw new Exception("Non-integer was passed to Profile validator!");
200
        }
201
        $temp = \core\ProfileFactory::instantiate($clean); // constructor throws an exception if NX, game over
202
203
        if ($idpIdentifier !== NULL && $temp->institution != $idpIdentifier) {
204
            throw new Exception($this->inputValidationError("The profile does not belong to the IdP!"));
205
        }
206
        return $temp;
207
    }
208
209
    /**
210
     * Checks if the input refers to a known DeploymentManaged. Optionally also takes an
211
     * IdP identifier and then checks if the Profile belongs to the referenced 
212
     * IdP
213
     * 
214
     * @param mixed     $input the numeric ID of the Deployment in the system
215
     * @param \core\IdP $idp   the IdP
216
     * @return \core\DeploymentManaged
217
     * @throws Exception
218
     */
219
    public function existingDeploymentManaged($input, $idp)
220
    {
221
        $clean = $this->integer($input);
222
        if ($clean === FALSE) {
223
            throw new Exception("Non-integer was passed to Profile validator!");
224
        }
225
        $temp = new \core\DeploymentManaged($idp, $clean); // constructor throws an exception if NX, game over
226
227
        if ($temp->institution != $idp->identifier) {
228
            throw new Exception($this->inputValidationError("The profile does not belong to the IdP!"));
229
        }
230
        return $temp;
231
    }
232
233
    /**
234
     * Checks if this is a device known to the system
235
     * @param mixed $input the name of the device (index in the Devices.php array)
236
     * @return string returns the same string on success, throws an Exception on failure
237
     * @throws Exception
238
     */
239
    public function existingDevice($input)
240
    {
241
        $devicelist = \devices\Devices::listDevices();
242
        $keyArray = array_keys($devicelist);
243
        if (!isset($devicelist[$input])) {
244
            throw new Exception($this->inputValidationError("This device does not exist!"));
245
        }
246
        $correctIndex = array_search($input, $keyArray);
247
        return $keyArray[$correctIndex];
248
    }
249
    
250
    /**
251
     * Test if a given external institution exists and that the provided userEmail
252
     * is listed as the admin for this institutution
253
     * @param string $extId
254
     * @param string $userEmail
255
     * @param string $ROid
256
     * @return int 1 if found 0 if not
257
     * @throws Exception
258
     */
259
    public function existingExtInstitution($extId, $userEmail = NULL, $ROid = NULL) {
260
        if ($ROid === NULL || !preg_match('/^[A-Z][A-Z]01$/', $ROid) ) {
261
            throw new Exception("$ROid: No correct federation identifier profided");
262
        }
263
        if ($userEmail === NULL) {
264
            throw new Exception("No $userEmail provided");
265
        }
266
        $extInst = new \core\ExternalEduroamDBData();
267
        return $extInst->verifyExternalEntity($ROid, $extId, $userEmail);
268
    }
269
270
    /**
271
     * Checks if the input was a valid string.
272
     * 
273
     * @param mixed   $input           a string to be made SQL-safe
274
     * @param boolean $allowWhitespace whether some whitespace (e.g. newlines should be preserved (true) or redacted (false)
275
     * @return string the massaged string
276
     * @throws Exception
277
     */
278
    public function string($input, $allowWhitespace = FALSE)
279
    {
280
        // always chop out invalid characters, and surrounding whitespace
281
        $retvalStep0 = iconv("UTF-8", "UTF-8//TRANSLIT", $input);
282
        if ($retvalStep0 === FALSE) {
283
            throw new Exception("iconv failure for string sanitisation. With TRANSLIT, this should never happen!");
284
        }
285
        $retvalStep1 = trim($retvalStep0);
286
        // if some funny person wants to inject markup tags, remove them
287
        if ($retvalStep1 !== '') {
288
            $retval = htmlspecialchars(strip_tags($retvalStep1), ENT_NOQUOTES);             
289
            if ($retval === '') {
290
                throw new Exception("filter_var failure for string sanitisation.");
291
            }
292
        } else {
293
            $retval = '';
294
        }
295
        // unless explicitly wanted, take away intermediate disturbing whitespace
296
        // a simple "space" is NOT disturbing :-)
297
        if ($allowWhitespace === FALSE) {
298
            $afterWhitespace = preg_replace('/(\0|\r|\x0b|\t|\n)/', '', $retval);
299
        } else {
300
            // even if we allow whitespace, not pathological ones!
301
            $afterWhitespace = preg_replace('/(\0|\r|\x0b)/', '', $retval);
302
        }
303
        if (is_array($afterWhitespace)) {
304
            throw new Exception("This function has to be given a string and returns a string. preg_replace has generated an array instead!");
305
        }
306
        return (string) $afterWhitespace;
307
    }
308
309
    /**
310
     * Is this an integer, or a string that represents an integer?
311
     * 
312
     * @param mixed $input the raw input
313
     * @return boolean|int returns the input, or FALSE if it is not an integer-like value
314
     */
315
    public function integer($input)
316
    {
317
        if (is_numeric($input)) {
318
            return (int) $input;
319
        }
320
        return FALSE;
321
    }
322
323
    /**
324
     * Is this a string representing a potentially more than 64-Bit length integer?
325
     * 
326
     * @param string $input the input data which is possibly a really large integer
327
     * @return boolean|string returns the input, or FALSE if it is not an integer-like string
328
     */
329
    public function hugeInteger($input)
330
    {
331
        if (is_numeric($input)) {
332
            return $input;
333
        }
334
        return FALSE;
335
    }
336
337
    /**
338
     * Checks if the input is the hex representation of a Consortium OI (i.e. three
339
     * or five bytes)
340
     * 
341
     * @param mixed $input the raw input
342
     * @return boolean|string returns the input, or FALSE on validation failure
343
     */
344
    public function consortiumOI($input)
345
    {
346
        $shallow = $this->string($input);
347
        if (strlen($shallow) != 6 && strlen($shallow) != 10) {
348
            return FALSE;
349
        }
350
        if (!preg_match("/^[a-fA-F0-9]+$/", $shallow)) {
351
            return FALSE;
352
        }
353
        return $shallow;
354
    }
355
356
    /**
357
     * Is the input an NAI realm? Throws HTML error and returns FALSE if not.
358
     * 
359
     * @param mixed $input the input to check
360
     * @return boolean|string returns the realm, or FALSE if it was malformed
361
     */
362
    public function realm($input)
363
    {
364
        \core\common\Entity::intoThePotatoes();
365
        if (strlen($input) == 0) {
366
            echo $this->inputValidationError(_("Realm is empty!"));
367
            \core\common\Entity::outOfThePotatoes();
368
            return FALSE;
369
        }
370
371
        // basic string checks
372
        $check = $this->string($input);
373
        // list of things to check, and the error they produce
374
        $pregCheck = [
375
            "/@/" => _("Realm contains an @ sign!"),
376
            "/^\./" => _("Realm begins with a . (dot)!"),
377
            "/\.$/" => _("Realm ends with a . (dot)!"),
378
            "/ /" => _("Realm contains spaces!"),
379
        ];
380
381
        // bark on invalid constructs
382
        foreach ($pregCheck as $search => $error) {
383
            if (preg_match($search, $check) == 1) {
384
                echo $this->inputValidationError($error);
385
                \core\common\Entity::outOfThePotatoes();
386
                return FALSE;
387
            }
388
        }
389
390
        if (preg_match("/\./", $check) == 0) {
391
            echo $this->inputValidationError(_("Realm does not contain at least one . (dot)!"));
392
            \core\common\Entity::outOfThePotatoes();
393
            return FALSE;
394
        }
395
396
        // none of the special HTML entities should be here. In case someone wants
397
        // to mount a CSS attack by providing something that matches the realm constructs
398
        // below but has interesting stuff between, mangle the input so that these
399
        // characters do not do any harm.
400
        \core\common\Entity::outOfThePotatoes();
401
        return htmlentities($check, ENT_QUOTES);
402
    }
403
404
    /**
405
     * could this be a valid username? 
406
     * 
407
     * Only checks correct form, not if the user actually exists in the system.
408
     * 
409
     * @param mixed $input the username
410
     * @return string echoes back the input string, or throws an Exception if bogus
411
     * @throws Exception
412
     */
413
    public function syntaxConformUser($input)
414
    {
415
        $retvalStep0 = iconv("UTF-8", "UTF-8//TRANSLIT", $input);
416
        if ($retvalStep0 === FALSE) {
417
            throw new Exception("iconv failure for string sanitisation. With TRANSLIT, this should never happen!");
418
        }
419
        $retvalStep1 = trim($retvalStep0);
420
421
        $retval = preg_replace('/(\0|\r|\x0b|\t|\n)/', '', $retvalStep1);
422
        if ($retval != "" && !ctype_print($retval)) {
423
            throw new Exception($this->inputValidationError("The user identifier is not an ASCII string!"));
424
        }
425
426
        return $retval;
427
    }
428
429
    /**
430
     * could this be a valid token? 
431
     * 
432
     * Only checks correct form, not if the token actually exists in the system.
433
     * @param mixed $input the raw input
434
     * @return string echoes back the input string, or throws an Exception if bogus
435
     * @throws Exception
436
     */
437
    public function token($input)
438
    {
439
        $retval = $input;
440
        if ($input != "" && preg_match('/[^0-9a-fA-F]/', $input) != 0) {
441
            throw new Exception($this->inputValidationError("Token is not a hexadecimal string!"));
442
        }
443
        return $retval;
444
    }
445
446
    /**
447
     * Is this be a valid coordinate vector on one axis?
448
     * 
449
     * @param mixed $input a numeric value in range of a geo coordinate [-180;180]
450
     * @return string returns back the input if all is good; throws an Exception if out of bounds or not numeric
451
     * @throws Exception
452
     */
453
    public function coordinate($input)
454
    {
455
        $oldlocale = setlocale(LC_NUMERIC, 0);
456
        setlocale(LC_NUMERIC, "en_GB");
457
        if (!is_numeric($input)) {
458
            throw new Exception($this->inputValidationError("Coordinate is not a numeric value!"));
459
        }
460
        setlocale(LC_NUMERIC, $oldlocale);
461
        // lat and lon are always in the range of [-180;+180]
462
        if ($input < -180 || $input > 180) {
463
            throw new Exception($this->inputValidationError("Coordinate is out of bounds. Which planet are you from?"));
464
        }
465
        return $input;
466
    }
467
468
    /**
469
     * Is this a valid coordinate pair in JSON encoded representation?
470
     * 
471
     * @param mixed $input the string to be checked: is this a serialised array with lat/lon keys in a valid number range?
472
     * @return string returns $input if checks have passed; throws an Exception if something's wrong
473
     * @throws Exception
474
     */
475
    public function coordJsonEncoded($input)
476
    {
477
        $tentative = json_decode($input, true);
478
        if (is_array($tentative)) {
479
            if (isset($tentative['lon']) && isset($tentative['lat']) && $this->coordinate($tentative['lon']) && $this->coordinate($tentative['lat'])) {
480
                return $input;
481
            }
482
        }
483
        throw new Exception($this->inputValidationError("Wrong coordinate encoding (2.0 uses JSON, not serialize)!"));
484
    }
485
486
    /**
487
     * This checks the state of a HTML GET/POST "boolean".
488
     * 
489
     * If not checked, no value is submitted at all; if checked, has the word "on". 
490
     * Anything else is a big error.
491
     * 
492
     * @param mixed $input the string to test
493
     * @return boolean TRUE if the input was "on". It is not possible in HTML to signal "off"
494
     * @throws Exception
495
     */
496
    public function boolean($input)
497
    {
498
        if ($input != "on") {
499
            throw new Exception($this->inputValidationError("Unknown state of boolean option!"));
500
        }
501
        return TRUE;
502
    }
503
504
    /**
505
     * checks if we have the strings "IdP" "SP" or "IdPSP"
506
     * 
507
     * @param string $partTypeRaw the string to be validated as participant type
508
     * @return string validated result
509
     * @throws Exception
510
     */
511
    public function partType($partTypeRaw)
512
    {
513
        switch ($partTypeRaw) {
514
            case \core\IdP::TYPE_IDP:
515
                return \core\IdP::TYPE_IDP;
516
            case \core\IdP::TYPE_SP:
517
                return \core\IdP::TYPE_SP;
518
            case \core\IdP::TYPE_IDPSP:
519
                return \core\IdP::TYPE_IDPSP;
520
            default:
521
                throw new Exception("Unknown Participant Type!");
522
        }
523
    }
524
525
    const TABLEMAPPING = [
526
        "IdP" => "institution_option",
527
        "Profile" => "profile_option",
528
        "FED" => "federation_option",
529
    ];
530
531
    /**
532
     * Is this a valid database reference? Has the form <tablename>-<rowID> and there
533
     * needs to be actual data at that place
534
     * 
535
     * @param string $input the reference to check
536
     * @return boolean|array the reference split up into "table" and "rowindex", or FALSE
537
     */
538
    public function databaseReference($input)
539
    {
540
        $pregMatches = [];
541
        if (preg_match("/^ROWID-(IdP|Profile|FED)-([0-9]+)$/", $input, $pregMatches) != 1) {
542
            return FALSE;
543
        }
544
        $rownumber = $this->integer($pregMatches[2]);
545
        if ($rownumber === FALSE) {
546
            return FALSE;
547
        }
548
        return ["table" => self::TABLEMAPPING[$pregMatches[1]], "rowindex" => $rownumber];
549
    }
550
551
    /**
552
     * is this a valid hostname?
553
     * 
554
     * @param mixed $input the raw input
555
     * @return boolean|string echoes the hostname, or FALSE if bogus
556
     */
557
    public function hostname($input)
558
    {
559
        // is it a valid IP address (IPv4 or IPv6), or a hostname?
560
        if (filter_var($input, FILTER_VALIDATE_IP) || filter_var($input, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME)) {
561
            // if it's a verified IP address or hostname then it does not contain
562
            // rubbish of course. But just to be sure, run htmlspecialchars around it
563
            return htmlspecialchars($input, ENT_QUOTES);
564
        }
565
        return FALSE;
566
    }
567
568
    /**
569
     * is this a valid email address?
570
     * 
571
     * @param mixed $input the raw input
572
     * @return boolean|string echoes the mail address, or FALSE if bogus
573
     */
574
    public function email($input)
575
    {
576
577
        if (filter_var($this->string($input), FILTER_VALIDATE_EMAIL)) {
578
            return $input;
579
        }
580
        // if we get here, it's bogus
581
        return FALSE;
582
    }
583
584
    /**
585
     * is this a well-formed SMS number? Light massaging - leading + will be removed
586
     * @param string $input the raw input
587
     * @return boolean|string
588
     */
589
    public function sms($input)
590
    {
591
        $number = str_replace(' ', '', str_replace(".", "", str_replace("+", "", $input)));
592
        if (!is_numeric($number)) {
593
            return FALSE;
594
        }
595
        return $number;
596
    }
597
598
    /**
599
     * Is this is a language we support? If not, sanitise to our configured default language.
600
     * 
601
     * @param mixed $input the candidate language identifier
602
     * @return string
603
     * @throws Exception
604
     */
605
    public function supportedLanguage($input)
606
    {
607
        if (!array_key_exists($input, \config\Master::LANGUAGES)) {
608
            return \config\Master::APPEARANCE['defaultlocale'];
609
        }
610
        // otherwise, use the inversion trick to convince Scrutinizer that this is
611
        // a vetted value
612
        $retval = array_search(\config\Master::LANGUAGES[$input], \config\Master::LANGUAGES);
613
        if ($retval === FALSE) {
614
            throw new Exception("Impossible: the value we are searching for does exist, because we reference it directly.");
615
        }
616
        return $retval;
617
    }
618
619
    /**
620
     * Makes sure we are not receiving a bogus option name. The called function throws
621
     * an assertion if the name is not known.
622
     * 
623
     * @param mixed $input the unvetted option name
624
     * @return string
625
     */
626
    public function optionName($input)
627
    {
628
        $object = \core\Options::instance();
629
        return $object->assertValidOptionName($input);
630
    }
631
632
    /**
633
     * Checks to see if the input is a valid image of sorts
634
     * 
635
     * @param mixed $binary blob that may or may not be a parseable image
636
     * @return boolean
637
     */
638
    public function image($binary)
639
    {
640
        if (class_exists('\\Gmagick')) { 
641
            $image = new \Gmagick(); 
642
        } else {
643
            $image = new \Imagick();
644
        }
645
        try {
646
            $image->readImageBlob($binary);
647
        } catch (\ImagickException $exception) {
648
            echo "Error" . $exception->getMessage();
649
            return FALSE;
650
        }
651
        // image survived the sanity check
652
        return TRUE;
653
    }
654
655
    /**
656
     * searches for values in GET and POST, and filters the value according to
657
     * which kind of data is expected
658
     * 
659
     * @param string $varName name of the variable in GET/POST
660
     * @param string $filter  which type of filter to apply (safe_text / int)
661
     * @return NULL|string|integer the returned value
662
     */
663
    public function simpleInputFilter($varName, $filter)
664
    {
665
        $safeText = ["options" => ["regexp" => "/^[\w\d-]+$/"]];
666
        switch ($filter) {
667
            case 'safe_text':
668
                $out = filter_input(INPUT_GET, $varName, FILTER_VALIDATE_REGEXP, $safeText) ?? filter_input(INPUT_POST, $varName, FILTER_VALIDATE_REGEXP, $safeText);
669
                break;
670
            case 'int':
671
                $out = filter_input(INPUT_GET, $varName, FILTER_VALIDATE_INT) ?? filter_input(INPUT_POST, $varName, FILTER_VALIDATE_INT);
672
                break;
673
            default:
674
                $out = NULL;
675
                break;
676
        }
677
        if ($out === false) { // an error occurred during the filter_input runs; make this NULL instead then
678
            $out = NULL;
679
        }
680
        return $out;
681
    }
682
683
}
684