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
* 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
*/
@ -37,14 +37,14 @@ final class MD_STD {
* Returns the real path of a relative file path. Throws an error rather than
* 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 {
$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.");
}
return $output;
@ -57,10 +57,10 @@ final class MD_STD {
*
* @see https://www.php.net/manual/en/function.mkdir.php
*
* @param string $pathname The directory path.
* @param integer $mode Permissions.
* @param boolean $recursive Allows the creation of nested directories
* specified in the pathname.
* @param non-empty-string $pathname The directory path.
* @param integer $mode Permissions.
* @param boolean $recursive Allows the creation of nested directories
* specified in the pathname.
*
* @return void
*/
@ -82,7 +82,7 @@ final class MD_STD {
*
* @see https://www.php.net/manual/en/function.unlink.php
*
* @param string $filename File path.
* @param non-empty-string $filename File path.
*
* @return void
*/
@ -97,9 +97,9 @@ final class MD_STD {
/**
* 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 {
@ -135,9 +135,10 @@ final class MD_STD {
/**
* 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 string $needle Potential start of $haystack.
* @param non-empty-string $haystack String to check.
* @param non-empty-string $needle Potential start of $haystack.
*
* @return boolean
*/
@ -155,8 +156,8 @@ final class MD_STD {
/**
* Function checking if a string starts with any input from the input array.
*
* @param string $haystack String to check.
* @param string[] $needles Array containing potential start values of $haystack.
* @param non-empty-string $haystack String to check.
* @param string[] $needles Array containing potential start values of $haystack.
*
* @return boolean
*/
@ -164,7 +165,7 @@ final class MD_STD {
$output = false;
foreach ($needles as $needle) {
$output = self::startsWith($haystack, $needle);
$output = \str_starts_with($haystack, $needle);
if ($output == true) {
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.
*
* @param 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 $subject The string or an array with strings to search and replace.
* @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 non-empty-string $subject The string or an array with strings to search and replace.
*
* @return string
*/
@ -208,7 +242,28 @@ final class MD_STD {
$output = \json_encode($value, $options, $depth);
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;
@ -234,10 +289,10 @@ final class MD_STD {
/**
* Initializes a curl request with the given presets.
*
* @param string $url URL to query.
* @param integer $timeout Timeout in milliseconds.
* @param non-empty-string $url URL to query.
* @param integer $timeout Timeout in milliseconds.
*
* @return resource
* @return CurlHandle
*/
public static function curl_init(string $url, int $timeout) {
@ -266,15 +321,20 @@ final class MD_STD {
/**
* Wrapper for curling contents from the web.
*
* @param string $url URL to query.
* @param integer $timeout Timeout in milliseconds.
* @param non-empty-string $url URL to query.
* @param integer $timeout Timeout in milliseconds.
* @param array<string> $headers HTTP headers to pass on.
* Analogous to CURLOPT_HTTPHEADER.
*
* @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_setopt($curl, CURLOPT_RETURNTRANSFER, true);
if (!empty($headers)) {
\curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
}
$result = \curl_exec($curl);
@ -293,55 +353,98 @@ final class MD_STD {
* 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.
*
* @param array<string> $urls URL to query.
* @param integer $timeout Timeout in milliseconds.
* @param non-empty-array<non-empty-string> $urls URL to query.
* @param integer $timeout Timeout in milliseconds.
* @param array<string> $headers HTTP headers to pass on.
* Analogous to CURLOPT_HTTPHEADER.
*
* @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())) {
throw new exception("Failed to set up multi handle");
}
$mh = \curl_multi_init();
$curl_array = [];
foreach($urls as $i => $url) {
$curl_array[$i] = self::curl_init($url, $timeout);
curl_setopt($curl_array[$i], CURLOPT_RETURNTRANSFER, true);
curl_multi_add_handle($mh, $curl_array[$i]);
\curl_setopt($curl_array[$i], CURLOPT_RETURNTRANSFER, true);
if (!empty($headers)) {
\curl_setopt($curl_array[$i], CURLOPT_HTTPHEADER, $headers);
}
\curl_multi_add_handle($mh, $curl_array[$i]);
}
$running = null;
do {
usleep(10000);
curl_multi_exec($mh, $running);
\usleep(10000);
\curl_multi_exec($mh, $running);
} while($running > 0);
$res = [];
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){
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;
}
/**
* 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.
*
* @param array<string> $allowed_languages Array containing all the languages for which
* there are translations.
* @param string $default_language Default language of the instance of MD.
* @param string $lang_variable Currently set language variable. Optional.
* @param boolean $strict_mode Whether to demand "de-de" (true) or "de" (false) Optional.
* @param non-empty-array<non-empty-string> $allowed_languages Array containing all the languages for which
* there are translations.
* @param non-empty-string $default_language Default language of the instance of MD.
* @param string $lang_variable Currently set language variable. Optional.
* @param boolean $strict_mode Whether to demand "de-de" (true) or "de" (false) Optional.
*
* @return string
*/
@ -374,9 +477,7 @@ final class MD_STD {
foreach ($accepted_languages as $accepted_language) {
// 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);
// phpcs:enable
// war die Syntax gültig?
if (!$res) {
@ -398,9 +499,7 @@ final class MD_STD {
}
// Bis der Sprachcode leer ist...
// phpcs:disable Squiz.PHP.DisallowSizeFunctionsInLoops
while (!empty($lang_code)) {
// phpcs:enable
// mal sehen, ob der Sprachcode angeboten wird
if (\in_array(\strtolower(\join('-', $lang_code)), $allowed_languages, true)) {
// Qualität anschauen
@ -430,7 +529,7 @@ final class MD_STD {
/**
* 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
*/
@ -453,11 +552,11 @@ final class MD_STD {
* @param integer $bytes A file size, e.g. returned from filesize().
* @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 {
$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);
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 {
$output = \openssl_random_pseudo_bytes($length);
if ($output === false) {
throw new Exception("Failed generating random pseudo bytes using openssl_random_pseudo_bytes");
}
return $output;
}
@ -523,7 +619,7 @@ final class MD_STD {
/**
* 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
*/
@ -541,11 +637,41 @@ 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.
*
* @param string $filepath File path of the file that needs to exist.
* @param string[] $accepted_mimetype Mime type the file should have.
* @param non-empty-string $filepath File path of the file that needs to exist.
* @param string[] $accepted_mimetype Mime type the file should have.
*
* @return void
*/
@ -572,7 +698,7 @@ final class MD_STD {
* Wrapper around exec, to be used with editing functions.
* 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
*/
@ -616,8 +742,56 @@ final class MD_STD {
*/
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;
}
/**
* 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 */
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.
*
@ -29,8 +43,8 @@ final class MD_STD_CACHE {
$outputT = trim(MD_STD::minimizeHTMLString(MD_STD::ob_get_clean()));
echo $outputT;
$redis = new Redis();
$redis->connect(self::$redis_host, self::$redis_port, 1, null, 0, 0, ['auth' => [MD_CONF::$redis_pw]]);
$redis = self::open_redis_default();
$redis->set($redisKey, $outputT);
$redis->expire($redisKey, $expiry);
$redis->close();
@ -52,8 +66,8 @@ final class MD_STD_CACHE {
return '';
}
$redis = new Redis();
$redis->connect(self::$redis_host, self::$redis_port, 1, null, 0, 0, ['auth' => [MD_CONF::$redis_pw]]);
$redis = self::open_redis_default();
if ($redis->ping() !== false) {
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);
/**
* Standard class providing overrides of default PHP functions as static
* functions.
* Encapsulates functions for handling inputs.
*/
final class MD_STD_IN {
/**
@ -17,7 +16,7 @@ final class MD_STD_IN {
*
* @return integer
*/
public static function sanitize_id($input):int {
public static function sanitize_id(mixed $input):int {
$input = \filter_var($input, \FILTER_VALIDATE_INT, [
'options' => [
@ -27,7 +26,7 @@ final class MD_STD_IN {
]
);
if (!($input)) {
if (!$input) {
throw new MDpageParameterNotNumericException("Value is not numeric.");
}
@ -42,7 +41,7 @@ final class MD_STD_IN {
*
* @return integer
*/
public static function sanitize_id_or_zero($input):int {
public static function sanitize_id_or_zero(mixed $input):int {
if ($input === "") {
return 0;
@ -72,7 +71,7 @@ final class MD_STD_IN {
*
* @return string
*/
public static function sanitize_text($input):string {
public static function sanitize_text(mixed $input):string {
$output = \filter_var($input,
FILTER_SANITIZE_STRING,
@ -96,14 +95,14 @@ final class MD_STD_IN {
*
* @return string
*/
public static function sanitize_rgb_color($input):string {
public static function sanitize_rgb_color(mixed $input):string {
$output = \filter_var($input,
FILTER_SANITIZE_STRING,
FILTER_FLAG_NO_ENCODE_QUOTES);
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);
}
@ -112,13 +111,32 @@ 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.
* If neither is given, returns a provided default.
*
* @param string $var_name Variable name.
* @param string $default Default value for the output.
* @param array<string> $allowed List of allowed values. Defaults to empty (all values allowed).
* @param non-empty-string $var_name Variable name.
* @param string $default Default value for the output.
* @param array<string> $allowed List of allowed values. Defaults to empty (all values allowed).
*
* @return string
*/
@ -146,9 +164,9 @@ final class MD_STD_IN {
* Retrieves HTTP input texts from POST variables.
* If none is given, returns a provided default.
*
* @param string $var_name Variable name.
* @param string $default Default value for the output.
* @param array<string> $allowed List of allowed values. Defaults to empty (all values allowed).
* @param non-empty-string $var_name Variable name.
* @param string $default Default value for the output.
* @param array<string> $allowed List of allowed values. Defaults to empty (all values allowed).
*
* @return string
*/
@ -176,7 +194,7 @@ final class MD_STD_IN {
*
* @return string
*/
public static function sanitize_url($input):string {
public static function sanitize_url(mixed $input):string {
if ($input === "") {
return "";
@ -198,7 +216,7 @@ final class MD_STD_IN {
*
* @return string
*/
public static function sanitize_email($input):string {
public static function sanitize_email(mixed $input):string {
if ($input === "") {
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.
*
@ -267,4 +306,84 @@ final class MD_STD_IN {
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);
/**
* Class providing static functions with basic security operations.
* Gathers wrappers for handling basic security operations.
*/
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.
*
@ -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 .
*
@ -62,7 +67,7 @@ final class MD_STD_SEC {
// 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.
$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
$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) ?: [];
// 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()];
}
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()];
}
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()];
}
// Increase counters and update timers
$loginLog['common']['count']++;
++$loginLog['common']['count'];
$loginLog['common']['time'] = \time();
$loginLog['usr'][$hash_user]['count']++;
++$loginLog['usr'][$hash_user]['count'];
$loginLog['usr'][$hash_user]['time'] = \time();
$loginLog['ip'][$hash_ip]['count']++;
++$loginLog['ip'][$hash_ip]['count'];
$loginLog['ip'][$hash_ip]['time'] = \time();
// 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
$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 {
$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)) {
$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;
};
}
}