Compare commits

...

34 Commits

Author SHA1 Message Date
aa7a3c5012 Clarify prevention of empty returns in array splitter 2021-11-30 00:45:02 +01:00
3f37dd7a9e Improve phpdoc types, type-safety 2021-11-29 22:30:28 +01:00
86c8235dae Specify MDJsonEncodingFailedException for failure to encode JSON through
MD_STD::json_encode
2021-11-26 03:23:07 +01:00
8f5174e90d Move to rather locking down based on user accounts than on IP in
MD_STD_SEC, use class constants for more obvious code
2021-11-25 01:09:08 +01:00
80af1ef260 Add function for opening redis connection using default settings 2021-11-22 23:44:53 +01:00
eb869071b8 Add class for splitting a list into lists of a predefined size 2021-11-16 14:40:38 +01:00
e19e0c875c Add function for batch validating ids in an array 2021-11-03 02:41:22 +01:00
245d161805 Add function stri_contains for case-insensitive, but intuitive
str_contains
2021-09-28 01:15:51 +02:00
a1e6d7773b Fix error in sorting a searched list by an inherent value 2021-09-26 19:25:07 +02:00
d35e3ed003 Fix error in sorting by external list 2021-09-26 19:23:42 +02:00
9113cad57e Add new class MD_STD_SORT to provide useful sorting tools 2021-09-26 01:02:18 +02:00
143a4680e2 Add validation function for phone numbers 2021-09-25 21:56:01 +02:00
a6ebab3e03 Remove superfluous parentheses 2021-09-17 15:52:37 +02:00
80ab3216d5 Allow setting headers when running MD_STD::runCurl 2021-08-12 13:57:25 +02:00
2071b57053 Add class MD_STD_DEBUG for debugging and code improvements 2021-07-27 23:44:08 +02:00
5e7313f166 Use MD_STD::json_encode over the generic json_encode 2021-07-20 18:25:34 +02:00
1c5d451619 Add json_encode_object for encoding objects (mainly SimpleXML obj.) 2021-07-20 18:15:23 +02:00
7fb5ad8ced Deprecate MD_STD::startsWith - can be replaced by str_starts_with 2021-07-20 12:51:46 +02:00
01ac23229b Use MD_STD::unlink over unlink in mime type check 2021-07-20 03:34:01 +02:00
d53303e617 Add option to pass acceptable mime types to
MD_STD_IN::move_uploaded_file
2021-07-20 02:04:55 +02:00
6adf0ee0a2 Add wrapper around move_uploaded_file 2021-07-20 01:26:31 +02:00
dbbdf4f230 Add function to ensure an input string is UTF-8 encoded 2021-07-01 15:34:46 +02:00
f030adba20 Set worker-src 'self' in MD_STD_SEC 2021-05-15 17:17:53 +02:00
980c408631 Remove explicit naming of domain in setcookie 2021-05-15 15:37:05 +02:00
a06a6ed41d Prepare for PHP 8 2021-05-13 23:02:37 +02:00
20c33437c9 Add function for checking the mime type of a remove file 2021-05-13 22:18:58 +02:00
63d6154d40 Fix spelling in setting language cookie
See #6
2021-05-13 15:21:06 +02:00
d03befe483 Use prefixed cookies for user language 2021-05-13 14:48:44 +02:00
fe0a8ba83b Remove obsolete phpcs exclusions 2021-05-01 22:54:37 +02:00
3b5f20aa96 Add missing "static" keyword in MD_STD 2021-04-14 18:16:13 +02:00
56f4fdc88a Add function get_user_lang for getting user language based on cookies 2021-04-14 17:59:11 +02:00
919ffdb1b5 Use ++$i over $i++
This slightly improves performance.
2021-04-11 21:20:44 +02:00
36bdb36986 Use consistent first uppercase char in MD_STD reference to exception 2021-04-09 13:45:19 +02:00
2c1f6a0490 Move scripts to /src subdirectory 2021-03-09 20:09:11 +01:00
9 changed files with 544 additions and 94 deletions

View File

@ -0,0 +1,19 @@
<?PHP
declare(strict_types = 1);
/**
* Reports a failure to encode JSON data.
*/
final class MDJsonEncodingFailedException extends Exception {
/**
* Error message.
*
* @return string
*/
public function errorMessage() {
//error message
$errorMsg = 'Failed to encode JSON data.';
return $errorMsg;
}
}

View File

@ -13,7 +13,7 @@ final class MD_STD {
* Wrapper around file_get_contents, that provides catches errors on it and returns * Wrapper around file_get_contents, that provides catches errors on it and returns
* with type safety. * with type safety.
* *
* @param string $filename Filepath of the file to read. * @param non-empty-string $filename Filepath of the file to read.
* *
* @return string * @return string
*/ */
@ -37,14 +37,14 @@ final class MD_STD {
* Returns the real path of a relative file path. Throws an error rather than * Returns the real path of a relative file path. Throws an error rather than
* returning the default false. * returning the default false.
* *
* @param string $path File path to convert. * @param non-empty-string $path File path to convert.
* *
* @return string * @return non-empty-string
*/ */
public static function realpath(string $path):string { public static function realpath(string $path):string {
$output = \realpath($path); $output = \realpath($path);
if (!\is_string($output)) { if (!\is_string($output) || empty($output)) {
throw new MDFileDoesNotExist("The file {$path} does not exist or is not readable."); throw new MDFileDoesNotExist("The file {$path} does not exist or is not readable.");
} }
return $output; return $output;
@ -57,7 +57,7 @@ final class MD_STD {
* *
* @see https://www.php.net/manual/en/function.mkdir.php * @see https://www.php.net/manual/en/function.mkdir.php
* *
* @param string $pathname The directory path. * @param non-empty-string $pathname The directory path.
* @param integer $mode Permissions. * @param integer $mode Permissions.
* @param boolean $recursive Allows the creation of nested directories * @param boolean $recursive Allows the creation of nested directories
* specified in the pathname. * specified in the pathname.
@ -82,7 +82,7 @@ final class MD_STD {
* *
* @see https://www.php.net/manual/en/function.unlink.php * @see https://www.php.net/manual/en/function.unlink.php
* *
* @param string $filename File path. * @param non-empty-string $filename File path.
* *
* @return void * @return void
*/ */
@ -97,9 +97,9 @@ final class MD_STD {
/** /**
* Gets contents of a folder. * Gets contents of a folder.
* *
* @param string $filepath Directory path. * @param non-empty-string $filepath Directory path.
* *
* @return array<string> * @return array<non-empty-string>
*/ */
public static function scandir(string $filepath):array { public static function scandir(string $filepath):array {
@ -135,9 +135,10 @@ final class MD_STD {
/** /**
* Function checking if a string starts with another. * Function checking if a string starts with another.
* DEPRECATED. Can be replaced by PHP8's str_starts_with.
* *
* @param string $haystack String to check. * @param non-empty-string $haystack String to check.
* @param string $needle Potential start of $haystack. * @param non-empty-string $needle Potential start of $haystack.
* *
* @return boolean * @return boolean
*/ */
@ -155,7 +156,7 @@ final class MD_STD {
/** /**
* Function checking if a string starts with any input from the input array. * Function checking if a string starts with any input from the input array.
* *
* @param string $haystack String to check. * @param non-empty-string $haystack String to check.
* @param string[] $needles Array containing potential start values of $haystack. * @param string[] $needles Array containing potential start values of $haystack.
* *
* @return boolean * @return boolean
@ -164,7 +165,7 @@ final class MD_STD {
$output = false; $output = false;
foreach ($needles as $needle) { foreach ($needles as $needle) {
$output = self::startsWith($haystack, $needle); $output = \str_starts_with($haystack, $needle);
if ($output == true) { if ($output == true) {
return $output; return $output;
} }
@ -173,12 +174,45 @@ final class MD_STD {
} }
/**
* Function checking if a string contains another in a case-insensitive way.
*
* @param non-empty-string $haystack String to check.
* @param non-empty-string $needle Potential start of $haystack.
*
* @return boolean
*/
public static function stri_contains(string $haystack, string $needle):bool {
if (stripos($haystack, $needle) === false) return false;
return true;
}
/**
* Function checking if a string contains any of a given set of strings in a
* case-insensitive way.
*
* @param non-empty-string $haystack String to check.
* @param array<non-empty-string> $needles Potential values contained in haystack.
*
* @return boolean
*/
public static function stri_contains_any(string $haystack, array $needles):bool {
foreach ($needles as $needle) {
if (self::stri_contains($haystack, $needle) === true) return true;
}
return false;
}
/** /**
* Type-safe(r) wrapper around preg_replace. * Type-safe(r) wrapper around preg_replace.
* *
* @param string $pattern The pattern to search for. It can be either a string or an array with strings. * @param non-empty-string $pattern The pattern to search for. It can be either a string or an array with strings.
* @param string $replacement To replace with. * @param string $replacement To replace with.
* @param string $subject The string or an array with strings to search and replace. * @param non-empty-string $subject The string or an array with strings to search and replace.
* *
* @return string * @return string
*/ */
@ -208,7 +242,28 @@ final class MD_STD {
$output = \json_encode($value, $options, $depth); $output = \json_encode($value, $options, $depth);
if ($output === false) { if ($output === false) {
throw new Exception("JSON output could not be generated"); throw new MDJsonEncodingFailedException("JSON output could not be generated");
}
return $output;
}
/**
* Type-safe wrapper around json_encode for objects.
*
* @see https://www.php.net/manual/en/function.json-encode.php
*
* @param object $value The value being encoded. Can be any type except a resource.
* @param integer $options Bitmask consisting of JSON_FORCE_OBJECT, JSON_HEX_QUOT ...
* @param integer $depth Depth of coding.
*
* @return string
*/
public static function json_encode_object(object $value, int $options = 0, int $depth = 512):string {
$output = \json_encode($value, $options, $depth);
if ($output === false) {
throw new MDJsonEncodingFailedException("JSON output could not be generated");
} }
return $output; return $output;
@ -234,10 +289,10 @@ final class MD_STD {
/** /**
* Initializes a curl request with the given presets. * Initializes a curl request with the given presets.
* *
* @param string $url URL to query. * @param non-empty-string $url URL to query.
* @param integer $timeout Timeout in milliseconds. * @param integer $timeout Timeout in milliseconds.
* *
* @return resource * @return CurlHandle
*/ */
public static function curl_init(string $url, int $timeout) { public static function curl_init(string $url, int $timeout) {
@ -266,15 +321,20 @@ final class MD_STD {
/** /**
* Wrapper for curling contents from the web. * Wrapper for curling contents from the web.
* *
* @param string $url URL to query. * @param non-empty-string $url URL to query.
* @param integer $timeout Timeout in milliseconds. * @param integer $timeout Timeout in milliseconds.
* @param array<string> $headers HTTP headers to pass on.
* Analogous to CURLOPT_HTTPHEADER.
* *
* @return string * @return string
*/ */
public static function runCurl(string $url, int $timeout = 1200):string { public static function runCurl(string $url, int $timeout = 1200, array $headers = []):string {
$curl = self::curl_init($url, $timeout); $curl = self::curl_init($url, $timeout);
\curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); \curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
if (!empty($headers)) {
\curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
}
$result = \curl_exec($curl); $result = \curl_exec($curl);
@ -293,53 +353,96 @@ final class MD_STD {
* Wrapper for curling multiple pages from the web at ones and returning their contents. * Wrapper for curling multiple pages from the web at ones and returning their contents.
* Adapted from hushuilong's comment at https://www.php.net/manual/de/function.curl-multi-init.php#105252. * Adapted from hushuilong's comment at https://www.php.net/manual/de/function.curl-multi-init.php#105252.
* *
* @param array<string> $urls URL to query. * @param non-empty-array<non-empty-string> $urls URL to query.
* @param integer $timeout Timeout in milliseconds. * @param integer $timeout Timeout in milliseconds.
* @param array<string> $headers HTTP headers to pass on.
* Analogous to CURLOPT_HTTPHEADER.
* *
* @return array<string> * @return array<string>
*/ */
public static function runCurlMulti(array $urls, int $timeout = 1200):array { public static function runCurlMulti(array $urls, int $timeout = 1200, array $headers = []):array {
if (!($mh = curl_multi_init())) { $mh = \curl_multi_init();
throw new exception("Failed to set up multi handle");
}
$curl_array = []; $curl_array = [];
foreach($urls as $i => $url) { foreach($urls as $i => $url) {
$curl_array[$i] = self::curl_init($url, $timeout); $curl_array[$i] = self::curl_init($url, $timeout);
curl_setopt($curl_array[$i], CURLOPT_RETURNTRANSFER, true); \curl_setopt($curl_array[$i], CURLOPT_RETURNTRANSFER, true);
curl_multi_add_handle($mh, $curl_array[$i]); if (!empty($headers)) {
\curl_setopt($curl_array[$i], CURLOPT_HTTPHEADER, $headers);
}
\curl_multi_add_handle($mh, $curl_array[$i]);
} }
$running = null; $running = null;
do { do {
usleep(10000); \usleep(10000);
curl_multi_exec($mh, $running); \curl_multi_exec($mh, $running);
} while($running > 0); } while($running > 0);
$res = []; $res = [];
foreach($urls as $i => $url) { foreach($urls as $i => $url) {
$res[$i] = curl_multi_getcontent($curl_array[$i]); $res[$i] = \curl_multi_getcontent($curl_array[$i]) ?: '';
} }
foreach($urls as $i => $url){ foreach($urls as $i => $url){
curl_multi_remove_handle($mh, $curl_array[$i]); \curl_multi_remove_handle($mh, $curl_array[$i]);
} }
curl_multi_close($mh); \curl_multi_close($mh);
return $res; return $res;
} }
/**
* Sets and returns user language based on a session cookie.
*
* @param non-empty-array<non-empty-string> $allowed_langs Allowed languages.
* @param non-empty-string $default_lang Default language.
*
* @return non-empty-string
*/
public static function get_user_lang(array $allowed_langs, string $default_lang):string {
$cookie_options = [
'expires' => self::strtotime("+1 month"),
'path' => '/',
'secure' => true,
'httponly' => true,
'samesite' => 'Lax' // None || Lax || Strict
];
if (isset($_GET['navlang']) and in_array($_GET['navlang'], $allowed_langs, true)) {
if (!setcookie('__Host-lang', $_GET['navlang'], $cookie_options)) {
throw new Exception("Failed to set language");
}
$lang = $_GET['navlang'];
}
else if (isset($_COOKIE['__Host-lang']) and in_array($_COOKIE['__Host-lang'], $allowed_langs, true)) {
$lang = $_COOKIE['__Host-lang'];
}
else {
$lang = self::lang_getfrombrowser($allowed_langs, $default_lang, "", false);
if (!setcookie('__Host-lang', $lang, $cookie_options)) {
throw new Exception("Failed to set language");
}
}
if (empty($lang)) return $default_lang;
return $lang;
}
/** /**
* Function lang_getfrombrowser gets the browser language based on HTTP headers. * Function lang_getfrombrowser gets the browser language based on HTTP headers.
* *
* @param array<string> $allowed_languages Array containing all the languages for which * @param non-empty-array<non-empty-string> $allowed_languages Array containing all the languages for which
* there are translations. * there are translations.
* @param string $default_language Default language of the instance of MD. * @param non-empty-string $default_language Default language of the instance of MD.
* @param string $lang_variable Currently set language variable. Optional. * @param string $lang_variable Currently set language variable. Optional.
* @param boolean $strict_mode Whether to demand "de-de" (true) or "de" (false) Optional. * @param boolean $strict_mode Whether to demand "de-de" (true) or "de" (false) Optional.
* *
@ -374,9 +477,7 @@ final class MD_STD {
foreach ($accepted_languages as $accepted_language) { foreach ($accepted_languages as $accepted_language) {
// Alle Infos über diese Sprache rausholen // Alle Infos über diese Sprache rausholen
// phpcs:disable Generic.Strings.UnnecessaryStringConcat
$res = \preg_match('/^([a-z]{1,8}(?:-[a-z]{1,8})*)(?:;\s*q=(0(?:\.[0-9]{1,3})?|1(?:\.0{1,3})?))?$/i', $accepted_language, $matches); $res = \preg_match('/^([a-z]{1,8}(?:-[a-z]{1,8})*)(?:;\s*q=(0(?:\.[0-9]{1,3})?|1(?:\.0{1,3})?))?$/i', $accepted_language, $matches);
// phpcs:enable
// war die Syntax gültig? // war die Syntax gültig?
if (!$res) { if (!$res) {
@ -398,9 +499,7 @@ final class MD_STD {
} }
// Bis der Sprachcode leer ist... // Bis der Sprachcode leer ist...
// phpcs:disable Squiz.PHP.DisallowSizeFunctionsInLoops
while (!empty($lang_code)) { while (!empty($lang_code)) {
// phpcs:enable
// mal sehen, ob der Sprachcode angeboten wird // mal sehen, ob der Sprachcode angeboten wird
if (\in_array(\strtolower(\join('-', $lang_code)), $allowed_languages, true)) { if (\in_array(\strtolower(\join('-', $lang_code)), $allowed_languages, true)) {
// Qualität anschauen // Qualität anschauen
@ -430,7 +529,7 @@ final class MD_STD {
/** /**
* Type-safe wrapper around filesize, if output is false, throws an error. * Type-safe wrapper around filesize, if output is false, throws an error.
* *
* @param string $filename File name. * @param non-empty-string $filename File name.
* *
* @return integer * @return integer
*/ */
@ -453,11 +552,11 @@ final class MD_STD {
* @param integer $bytes A file size, e.g. returned from filesize(). * @param integer $bytes A file size, e.g. returned from filesize().
* @param integer $decimals Number of decimal digits to allow. * @param integer $decimals Number of decimal digits to allow.
* *
* @return string * @return non-empty-string
*/ */
public static function human_filesize(int $bytes, int $decimals = 2):string { public static function human_filesize(int $bytes, int $decimals = 2):string {
$size = ['B','kB','MB','GB','TB','PB','EB','ZB','YB']; $size = ['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
$factor = \floor((\strlen((string)$bytes) - 1) / 3); $factor = \floor((\strlen((string)$bytes) - 1) / 3);
return \sprintf("%.{$decimals}f", $bytes / \pow(1024, $factor)) . $size[$factor]; return \sprintf("%.{$decimals}f", $bytes / \pow(1024, $factor)) . $size[$factor];
@ -473,9 +572,6 @@ final class MD_STD {
public static function openssl_random_pseudo_bytes(int $length):string { public static function openssl_random_pseudo_bytes(int $length):string {
$output = \openssl_random_pseudo_bytes($length); $output = \openssl_random_pseudo_bytes($length);
if ($output === false) {
throw new Exception("Failed generating random pseudo bytes using openssl_random_pseudo_bytes");
}
return $output; return $output;
} }
@ -523,7 +619,7 @@ final class MD_STD {
/** /**
* Wrapper around the finfo functions to get the mime content type of a file. * Wrapper around the finfo functions to get the mime content type of a file.
* *
* @param string $filepath Expected file path. * @param non-empty-string $filepath Expected file path.
* *
* @return string * @return string
*/ */
@ -541,10 +637,40 @@ final class MD_STD {
} }
/**
* Wrapper around the finfo functions to get the mime content type of a file.
*
* @param non-empty-string $url Expected file path.
*
* @return string
*/
public static function remote_mime_content_type(string $url):string {
if (empty($tmp_file = \tempnam(\sys_get_temp_dir(), "remote_mime_type_check"))) {
throw new Exception("Failed to get temporary file location");
}
$fp = \fopen($tmp_file, 'w');
if (!($ch = \curl_init($url))) {
throw new Exception("Failed to initialize curl for $url");
};
\curl_setopt($ch, CURLOPT_FILE, $fp);
\curl_exec($ch);
\curl_close($ch);
$mime_type = MD_STD::mime_content_type($tmp_file);
self::unlink($tmp_file);
return $mime_type;
}
/** /**
* Checks if a file exists, with one of the expected mime types. * Checks if a file exists, with one of the expected mime types.
* *
* @param string $filepath File path of the file that needs to exist. * @param non-empty-string $filepath File path of the file that needs to exist.
* @param string[] $accepted_mimetype Mime type the file should have. * @param string[] $accepted_mimetype Mime type the file should have.
* *
* @return void * @return void
@ -572,7 +698,7 @@ final class MD_STD {
* Wrapper around exec, to be used with editing functions. * Wrapper around exec, to be used with editing functions.
* Pipes STDERR to STDOUT and throws an Exception on any error. * Pipes STDERR to STDOUT and throws an Exception on any error.
* *
* @param string $cmds Commands to run. * @param non-empty-string $cmds Commands to run.
* *
* @return void * @return void
*/ */
@ -616,8 +742,56 @@ final class MD_STD {
*/ */
public static function string_to_color_code(string $str):string { public static function string_to_color_code(string $str):string {
$code = substr(dechex(crc32($str)), 0, 6); $code = \substr(\dechex(\crc32($str)), 0, 6);
return $code; return $code;
} }
/**
* Checks if a directory is writable.
*
* @param non-empty-string $dir Directory path.
*
* @return void
*/
public static function check_is_writable(string $dir):void {
if (\is_dir($dir) === false) {
throw new MDFileDoesNotExist("Directory " . $dir . " does not exist");
}
if (\is_writable($dir) === false) {
throw new MDFileIsNotWritable("Directory " . $dir . " is not writable");
}
}
/**
* Splits an lists of integers into parts of a predefined size and returns those
* as sub-lists of a superordinate list.
*
* @param array<int, int> $input Input array.
* @param positive-int $size Size of each part of the return set.
*
* @return array<non-empty-array<integer>>
*/
public static function split_int_array_into_sized_parts(array $input, int $size):array {
/*
if ($size < 1) throw new Exception("Size of the target array must be a positive integer");
*/
$output = [];
$max = count($input);
$offset = 0;
while ($offset < $max) {
$cur = array_slice($input, $offset, $size - 1);
if (!empty($cur)) $output[] = $cur;
$offset += $size;
}
return $output;
}
} }

View File

@ -16,6 +16,20 @@ final class MD_STD_CACHE {
/** @var integer */ /** @var integer */
public static int $redis_port = 6379; public static int $redis_port = 6379;
/**
* Opens a connection to redis.
*
* @return Redis
*/
public static function open_redis_default():Redis {
$redis = new Redis();
$redis->connect(self::$redis_host, self::$redis_port, 1, null, 0, 0, ['auth' => [MD_CONF::$redis_pw]]);
return $redis;
}
/** /**
* Shutdown function for caching contents of output buffer. * Shutdown function for caching contents of output buffer.
* *
@ -29,8 +43,8 @@ final class MD_STD_CACHE {
$outputT = trim(MD_STD::minimizeHTMLString(MD_STD::ob_get_clean())); $outputT = trim(MD_STD::minimizeHTMLString(MD_STD::ob_get_clean()));
echo $outputT; echo $outputT;
$redis = new Redis(); $redis = self::open_redis_default();
$redis->connect(self::$redis_host, self::$redis_port, 1, null, 0, 0, ['auth' => [MD_CONF::$redis_pw]]);
$redis->set($redisKey, $outputT); $redis->set($redisKey, $outputT);
$redis->expire($redisKey, $expiry); $redis->expire($redisKey, $expiry);
$redis->close(); $redis->close();
@ -52,8 +66,8 @@ final class MD_STD_CACHE {
return ''; return '';
} }
$redis = new Redis(); $redis = self::open_redis_default();
$redis->connect(self::$redis_host, self::$redis_port, 1, null, 0, 0, ['auth' => [MD_CONF::$redis_pw]]);
if ($redis->ping() !== false) { if ($redis->ping() !== false) {
ob_start(); ob_start();

35
src/MD_STD_DEBUG.php Normal file
View File

@ -0,0 +1,35 @@
<?PHP
/**
* Provides basic debugging functions.
*/
declare(strict_types = 1);
/**
* Standard class providing simple and generally applicable
* debugging and code improvement functions.
*/
final class MD_STD_DEBUG {
/**
* Function simpleBenchmark prints the difference between start time and the time at the exit of script
* Should be put very early in the script.
*
* @return void
*/
public static function simpleBenchmark():void {
$start = \microtime(true);
\register_shutdown_function(function($start) :void {
echo PHP_EOL . '<pre>';
echo \microtime(true) - $start . "<br/>";
echo 'RAM Usage (Peak): ' . \memory_get_peak_usage() . "<br/>";
echo 'RAM Usage: ' . \memory_get_usage() . "<br/>";
if ($loadAvg = \sys_getloadavg()) {
echo 'Load avg. (last 1 minute): ' . $loadAvg[0] . '<br />';
echo 'Load avg. (last 5 minute): ' . $loadAvg[1] . '<br />';
echo 'Load avg. (last 15 minute): ' . $loadAvg[2] . '<br />';
}
echo '</pre>';
}, $start);
}
}

View File

@ -5,8 +5,7 @@
declare(strict_types = 1); declare(strict_types = 1);
/** /**
* Standard class providing overrides of default PHP functions as static * Encapsulates functions for handling inputs.
* functions.
*/ */
final class MD_STD_IN { final class MD_STD_IN {
/** /**
@ -17,7 +16,7 @@ final class MD_STD_IN {
* *
* @return integer * @return integer
*/ */
public static function sanitize_id($input):int { public static function sanitize_id(mixed $input):int {
$input = \filter_var($input, \FILTER_VALIDATE_INT, [ $input = \filter_var($input, \FILTER_VALIDATE_INT, [
'options' => [ 'options' => [
@ -27,7 +26,7 @@ final class MD_STD_IN {
] ]
); );
if (!($input)) { if (!$input) {
throw new MDpageParameterNotNumericException("Value is not numeric."); throw new MDpageParameterNotNumericException("Value is not numeric.");
} }
@ -42,7 +41,7 @@ final class MD_STD_IN {
* *
* @return integer * @return integer
*/ */
public static function sanitize_id_or_zero($input):int { public static function sanitize_id_or_zero(mixed $input):int {
if ($input === "") { if ($input === "") {
return 0; return 0;
@ -72,7 +71,7 @@ final class MD_STD_IN {
* *
* @return string * @return string
*/ */
public static function sanitize_text($input):string { public static function sanitize_text(mixed $input):string {
$output = \filter_var($input, $output = \filter_var($input,
FILTER_SANITIZE_STRING, FILTER_SANITIZE_STRING,
@ -96,14 +95,14 @@ final class MD_STD_IN {
* *
* @return string * @return string
*/ */
public static function sanitize_rgb_color($input):string { public static function sanitize_rgb_color(mixed $input):string {
$output = \filter_var($input, $output = \filter_var($input,
FILTER_SANITIZE_STRING, FILTER_SANITIZE_STRING,
FILTER_FLAG_NO_ENCODE_QUOTES); FILTER_FLAG_NO_ENCODE_QUOTES);
if ($output === false if ($output === false
|| ((preg_match('/^[a-zA-Z0-9]{3}$/', $output)) === false && (preg_match('/^[a-zA-Z0-9]{6}$/', $output)) === false) || (preg_match('/^[a-zA-Z0-9]{3}$/', $output) === false && preg_match('/^[a-zA-Z0-9]{6}$/', $output) === false)
) { ) {
throw new MDInvalidColorCode("Invalid color code provided: " . $output); throw new MDInvalidColorCode("Invalid color code provided: " . $output);
} }
@ -112,11 +111,30 @@ final class MD_STD_IN {
} }
/**
* Valiates a list of entries, ensuring all returned values are valid IDs.
*
* @param array<mixed> $inputs Input array.
*
* @return array<integer>
*/
public static function sanitize_id_array(array $inputs):array {
$output = [];
foreach ($inputs as $input) {
$output[] = self::sanitize_id($input);
}
return $output;
}
/** /**
* Retrieves HTTP input texts from GET or POST variables, whatever is provided. * Retrieves HTTP input texts from GET or POST variables, whatever is provided.
* If neither is given, returns a provided default. * If neither is given, returns a provided default.
* *
* @param string $var_name Variable name. * @param non-empty-string $var_name Variable name.
* @param string $default Default value for the output. * @param string $default Default value for the output.
* @param array<string> $allowed List of allowed values. Defaults to empty (all values allowed). * @param array<string> $allowed List of allowed values. Defaults to empty (all values allowed).
* *
@ -146,7 +164,7 @@ final class MD_STD_IN {
* Retrieves HTTP input texts from POST variables. * Retrieves HTTP input texts from POST variables.
* If none is given, returns a provided default. * If none is given, returns a provided default.
* *
* @param string $var_name Variable name. * @param non-empty-string $var_name Variable name.
* @param string $default Default value for the output. * @param string $default Default value for the output.
* @param array<string> $allowed List of allowed values. Defaults to empty (all values allowed). * @param array<string> $allowed List of allowed values. Defaults to empty (all values allowed).
* *
@ -176,7 +194,7 @@ final class MD_STD_IN {
* *
* @return string * @return string
*/ */
public static function sanitize_url($input):string { public static function sanitize_url(mixed $input):string {
if ($input === "") { if ($input === "") {
return ""; return "";
@ -198,7 +216,7 @@ final class MD_STD_IN {
* *
* @return string * @return string
*/ */
public static function sanitize_email($input):string { public static function sanitize_email(mixed $input):string {
if ($input === "") { if ($input === "") {
return ""; return "";
@ -213,6 +231,27 @@ final class MD_STD_IN {
} }
/**
* Sanitizes and validates a phone number. An empty string passes.
*
* @param mixed $input Input string.
*
* @return string
*/
public static function validate_phone_number(mixed $input):string {
if ($input === "") {
return "";
}
if (!preg_match("#^[()0-9/ +-]+$#", $input)) {
throw new MDgenericInvalidInputsException("Invalid phone number entered.");
}
return $input;
}
/** /**
* Sanitizes a string to a float. * Sanitizes a string to a float.
* *
@ -267,4 +306,84 @@ final class MD_STD_IN {
throw new MDgenericInvalidInputsException("ISBNs must be either 10 or 13 characters long."); throw new MDgenericInvalidInputsException("ISBNs must be either 10 or 13 characters long.");
} }
/**
* Returns an UTF8 version of a string.
*
* @param string $input Input string.
*
* @return string
*/
public static function ensureStringIsUtf8(string $input):string {
// If the input is valid UTF8 from the start, it is simply returned in its
// original form.
if (\mb_check_encoding($input, 'UTF-8')) {
return $input;
}
// To detect and convert the encoding for non-UTF8 strings, the list of
// encodings known to PHP's mbstring functions is checked against the input string.
// If any encoding matches the string, it will be converted to UTF8 accordingly.
$suitableEncodings = [];
$encodings = \mb_list_encodings();
foreach ($encodings as $encoding) {
if (\mb_detect_encoding($input, $encoding, true) !== false) {
$suitableEncodings[] = $encoding;
}
}
// If ISO-8859-1 is in the list of suitable encodings, try to convert with that.
if (\in_array('ISO-8859-1', $suitableEncodings, true)) {
if (($converted = \iconv('ISO-8859-1', "UTF-8//TRANSLIT", $input)) !== false) {
return $converted;
}
}
// If a conversion from ISO-8859-1 doesn't work, just take any of the other ones.
$suitableEncodings = \array_reverse($suitableEncodings);
foreach ($suitableEncodings as $encoding) {
if (($converted = \iconv($encoding, "UTF-8//TRANSLIT", $input)) !== false) {
return $converted;
}
}
/*
if (count($suitableEncodings) === 1) {
return mb_convert_encoding($input, 'UTF-8', );
}
*/
return $input;
}
/**
* Wrapper around move_uploaded_file that throws errors in case the upload failed
* for an identifiable reason.
*
* @param non-empty-string $filename Name of the file to upload.
* @param non-empty-string $destination Destination to move the file to.
* @param array<string> $mime_types Optional array of acceptable mime types. If this is
* not empty, the file will be checked for having one
* of the given mime types. If it does not, an error
* will be thrown.
*
* @return boolean
*/
public static function move_uploaded_file(string $filename, string $destination, array $mime_types = []):bool {
MD_STD::ensure_file($filename, $mime_types);
if (empty($destDir = dirname($destination))) {
return false;
}
MD_STD::check_is_writable($destDir);
if (!(\move_uploaded_file($filename, $destination))) {
return false;
}
return true;
}
} }

View File

@ -5,9 +5,19 @@
declare(strict_types = 1); declare(strict_types = 1);
/** /**
* Class providing static functions with basic security operations. * Gathers wrappers for handling basic security operations.
*/ */
final class MD_STD_SEC { final class MD_STD_SEC {
const REFRESH_TIME_GENERAL = 60; // Time until the comp. with the whole service is cleared.
const REFRESH_TIME_USER = 600; // Time until the comp. with the same username service is cleared.
const REFRESH_TIME_IP = 180; // Time until the comp. with the same IP is cleared. This should be lower than the user-level one, as people working together may be using a common IP.
const BRUTE_FORCE_DELAY_DEFAULT = 2000; // 2000 microseconds = 2 milliseconds
const BRUTE_FORCE_DELAY_MULTIPLIER_COMMON = 1.08;
const BRUTE_FORCE_DELAY_MULTIPLIER_PER_USER = 2.8;
const BRUTE_FORCE_DELAY_MULTIPLIER_PER_IP = 2;
/** /**
* Function for retrieving the anti-csrf token or generating it if need be. * Function for retrieving the anti-csrf token or generating it if need be.
* *
@ -45,11 +55,6 @@ final class MD_STD_SEC {
} }
const BRUTE_FORCE_DELAY_DEFAULT = 2000; // 2000 microseconds = 2 milliseconds
const BRUTE_FORCE_DELAY_MULTIPLIER_COMMON = 1.08;
const BRUTE_FORCE_DELAY_MULTIPLIER_PER_USER = 1.8;
const BRUTE_FORCE_DELAY_MULTIPLIER_PER_IP = 4;
/** /**
* Prevent brute force attacks by delaying the login . * Prevent brute force attacks by delaying the login .
* *
@ -62,7 +67,7 @@ final class MD_STD_SEC {
// Unstable but working way to get the user's IP. If the IP is falsified, // Unstable but working way to get the user's IP. If the IP is falsified,
// this can't be found out anyway and security is established by _common. // this can't be found out anyway and security is established by _common.
$ip = \strval($_SERVER['REMOTE_ADDR'] ?: ($_SERVER['HTTP_X_FORWARDED_FOR'] ?: $_SERVER['HTTP_CLIENT_IP'])); $ip = \filter_var($_SERVER['REMOTE_ADDR'] ?: ($_SERVER['HTTP_X_FORWARDED_FOR'] ?: $_SERVER['HTTP_CLIENT_IP']), \FILTER_VALIDATE_IP) ?: "Failed to find";
// Set name of log file // Set name of log file
$logfile_common = \sys_get_temp_dir() . "/logins_{$tool_name}.json"; $logfile_common = \sys_get_temp_dir() . "/logins_{$tool_name}.json";
@ -81,26 +86,26 @@ final class MD_STD_SEC {
$loginLog = \json_decode(MD_STD::file_get_contents($logfile_common), \true) ?: []; $loginLog = \json_decode(MD_STD::file_get_contents($logfile_common), \true) ?: [];
// Ensure the counters exist and aren't old than 600 seconds / 10 minutes // Ensure the counters exist and aren't old than 600 seconds / 10 minutes
if (empty($loginLog['common']) || \time() - $loginLog['common']['time'] > 600) { if (empty($loginLog['common']) || \time() - $loginLog['common']['time'] > self::REFRESH_TIME_GENERAL) {
$loginLog['common'] = ["count" => 0, "time" => \time()]; $loginLog['common'] = ["count" => 0, "time" => \time()];
} }
if (empty($loginLog['usr'][$hash_user]) || \time() - $loginLog['usr'][$hash_user]['time'] > 600) { if (empty($loginLog['usr'][$hash_user]) || \time() - $loginLog['usr'][$hash_user]['time'] > self::REFRESH_TIME_USER) {
$loginLog['usr'][$hash_user] = ["count" => 0, "time" => \time()]; $loginLog['usr'][$hash_user] = ["count" => 0, "time" => \time()];
} }
if (empty($loginLog['ip'][$hash_ip]) || \time() - $loginLog['ip'][$hash_ip]['time'] > 600) { if (empty($loginLog['ip'][$hash_ip]) || \time() - $loginLog['ip'][$hash_ip]['time'] > self::REFRESH_TIME_IP) {
$loginLog['ip'][$hash_ip] = ["count" => 0, "time" => \time()]; $loginLog['ip'][$hash_ip] = ["count" => 0, "time" => \time()];
} }
// Increase counters and update timers // Increase counters and update timers
$loginLog['common']['count']++; ++$loginLog['common']['count'];
$loginLog['common']['time'] = \time(); $loginLog['common']['time'] = \time();
$loginLog['usr'][$hash_user]['count']++; ++$loginLog['usr'][$hash_user]['count'];
$loginLog['usr'][$hash_user]['time'] = \time(); $loginLog['usr'][$hash_user]['time'] = \time();
$loginLog['ip'][$hash_ip]['count']++; ++$loginLog['ip'][$hash_ip]['count'];
$loginLog['ip'][$hash_ip]['time'] = \time(); $loginLog['ip'][$hash_ip]['time'] = \time();
// Update the log file // Update the log file
\file_put_contents($logfile_common, \json_encode($loginLog)); \file_put_contents($logfile_common, MD_STD::json_encode($loginLog));
// Translate counters into delay multipliers // Translate counters into delay multipliers
$delay_multiplier_common = $loginLog['common']['count']; $delay_multiplier_common = $loginLog['common']['count'];
@ -136,7 +141,7 @@ final class MD_STD_SEC {
*/ */
public static function sendContentSecurityPolicy(array $directives, string $frame_ancestors = ""):void { public static function sendContentSecurityPolicy(array $directives, string $frame_ancestors = ""):void {
$policy = 'Content-Security-Policy: default-src ' . $directives['default-src'] . '; connect-src ' . $directives['connect-src'] . '; script-src ' . $directives['script-src'] . '; img-src ' . $directives['img-src'] . '; media-src ' . $directives['media-src'] . '; style-src ' . $directives['style-src'] . '; font-src \'self\'; frame-src ' . $directives['frame-src'] . '; object-src ' . $directives['object-src'] . '; base-uri ' . $directives['base-uri'] . '; form-action ' . $directives['form-action'] . '; manifest-src \'self\';'; $policy = 'Content-Security-Policy: default-src ' . $directives['default-src'] . '; connect-src ' . $directives['connect-src'] . '; script-src ' . $directives['script-src'] . '; img-src ' . $directives['img-src'] . '; media-src ' . $directives['media-src'] . '; style-src ' . $directives['style-src'] . '; font-src \'self\'; frame-src ' . $directives['frame-src'] . '; object-src ' . $directives['object-src'] . '; base-uri ' . $directives['base-uri'] . '; form-action ' . $directives['form-action'] . '; manifest-src \'self\'; worker-src \'self\';';
if (!empty($frame_ancestors)) { if (!empty($frame_ancestors)) {
$policy .= ' frame-ancestors ' . $frame_ancestors . ';'; $policy .= ' frame-ancestors ' . $frame_ancestors . ';';

84
src/MD_STD_SORT.php Normal file
View File

@ -0,0 +1,84 @@
<?PHP
/**
* Provides basic helper functions for sorting.
*/
declare(strict_types = 1);
/**
* Standard class providing basic helper functions for sorting.
*/
final class MD_STD_SORT {
/**
* Provides a function usable by usort that sorts first by string occurence,
* then by a similarity as provided by an outside array.
*
* @param string $nameIndex Index of the name column.
* @param string $searchValue Value to search for.
* @param string $sortIndex Index to sort by in case the name index didn't return a hit.
*
* @return closure
*/
public static function sort_by_string_occurence_and_int(string $nameIndex, string $searchValue, string $sortIndex):closure {
return function (array $a, array $b) use ($nameIndex, $searchValue, $sortIndex) {
if ($a == $b) {
return 0;
}
if ($a[$nameIndex] === $searchValue) return -1;
if ($b[$nameIndex] === $searchValue) return 1;
$containsSearchA = stripos($a[$nameIndex], $searchValue);
$containsSearchB = stripos($b[$nameIndex], $searchValue);
if ($containsSearchA !== false and $containsSearchB !== false) {
return $a[$sortIndex] > $b[$sortIndex] ? -1 : 1;
}
if ($containsSearchA !== false) return -1;
if ($containsSearchB !== false) return 1;
return $a[$sortIndex] > $b[$sortIndex] ? -1 : 1;
};
}
/**
* Provides a function usable by usort that sorts first by string occurence,
* then by an integer value provided in another part of the array.
*
* @param string $nameIndex Index of the name column.
* @param string $searchValue Value to search for.
* @param array<integer|float> $extSortBy External list to sort by, e.g.
* similarities as per levinsthein.
* @param string $sortIndex Index to sort by in case the name
* index didn't return a hit.
*
*
* @return closure
*/
public static function sort_by_string_occurence_and_ext_sort_index(string $nameIndex, string $searchValue, array $extSortBy, string $sortIndex):closure {
return function (array $a, array $b) use ($nameIndex, $searchValue, $extSortBy, $sortIndex) {
if ($a == $b) {
return 0;
}
if ($a[$nameIndex] === $searchValue) return -1;
if ($b[$nameIndex] === $searchValue) return 1;
$containsSearchA = stripos($a[$nameIndex], $searchValue);
$containsSearchB = stripos($b[$nameIndex], $searchValue);
if ($containsSearchA !== false and $containsSearchB !== false) {
return $extSortBy[$a[$sortIndex]] > $extSortBy[$b[$sortIndex]] ? -1 : 1;
}
if ($containsSearchA !== false) return -1;
if ($containsSearchB !== false) return 1;
return $extSortBy[$a[$sortIndex]] > $extSortBy[$b[$sortIndex]] ? -1 : 1;
};
}
}