Files
人事系统开发 1ba6efd8ed MRBS 1.12.2 等保2.0二级整改完整提交
包含:登录失败锁定、90天密码有效期、30分钟会话超时、
强制改密、登录审计日志、屏幕水印、企业背景图、
备案信息固定底部、favicon、JS空集合保护、
会话过期体验优化(403 JSON)、display_errors 关闭、
固定 key 根治 Integrity check failed 等全部改动

注意:config.inc.php/.htaccess/.user.ini 含敏感信息,
通过 .gitignore 排除,勿推送到公开仓库。
2026-09-09 16:55:02 +08:00

335 lines
7.1 KiB
PHP

<?php
declare(strict_types=1);
namespace MRBS\Utf8;
use InvalidArgumentException;
use Iterator;
use MRBS\System;
use RuntimeException;
/**
* A class that allows iteration over the characters in a UTF-8 string.
* It also has the methods:
* * toArray() converts the string into an array of UTF-8 characters
* * toUtf16() converts the string to UTF-16
*/
class Utf8String implements Iterator
{
private $byte_index;
private $char_index;
private $next_char_length;
private $have_all_chars = false;
private $data = [];
private $string;
// Note that we cannot use the PHP string functions, eg strlen() and strpos(),
// or their mb_ equivalents, in this class, because it is used by MRBS's
// emulations of the mb_ string functions.
public function __construct(string $string)
{
$this->string = $string;
$this->rewind();
}
/**
* Return the current element
*/
public function current() : ?string
{
return $this->data[$this->char_index] ?? null;
}
/**
* Move forward to next element
*/
public function next() : void
{
$this->byte_index += $this->next_char_length;
$this->char_index++;
$this->next_char_length = $this->nextCharLength();
$this->setNextChar();
}
/**
* Return the key of the current element
*/
public function key(): int
{
return $this->char_index;
}
/**
* Checks if current position is valid
*/
public function valid() : bool
{
return isset($this->data[$this->char_index]);
}
/**
* Rewind the Iterator to the first element
*/
public function rewind() : void
{
$this->byte_index = 0;
$this->char_index = 0;
$this->next_char_length = $this->nextCharLength();
$this->setNextChar();
}
/**
* Returns the string's length in bytes
*/
public function byteCount() : int
{
// Trivial case
if (!isset($this->string[0]))
{
return 0;
}
// Use a binary chop
$lower = 0;
$upper = 2;
while (($upper - $lower) > 1)
{
if (isset($this->string[$upper]))
{
$lower = $upper;
$upper = $upper * 2;
}
else
{
$upper = $lower + intval(($upper - $lower)/2);
}
}
$index = (isset($this->string[$upper])) ? $upper : $lower;
return $index + 1;
}
/**
* Converts the string to an array
*
* @param int $break_point provided for testing purposes only
*/
public function toArray(int $break_point=0) : array
{
// The $break_point parameter is there for testing purposes only. Historically
// MRBS provided utf8_substr() and utf8_substr_old(), using the preg_match_all()
// approach for strings longer than 1000 bytes and doing it manually otherwise,
// using an algorithm obtained from a contribution by "frank at jkelloggs dot dk"
// in the PHP online manual for substr() which testing had shown was faster.
//
// However, testing of the method below shows that the preg_match_all() approach
// is faster for all string lengths (maybe because the performance of preg_match_all()
// has been improved?). The code for the manual method is left in place just in case
// it is needed in the future.
if (!$this->have_all_chars)
{
if (strlen($this->string) > $break_point)
{
if (false === preg_match_all("/./su", $this->string, $matches))
{
throw new RuntimeException("preg_match_all() failed");
}
$this->data = $matches[0];
}
else
{
// Alternative method of splitting the string for small strings
$this->getRemainingData();
}
$this->have_all_chars = true;
}
return $this->data;
}
/**
* Convert to UTF-16
*/
public function toUtf16(?int $endianness=null, bool $strip_bom=false) : string
{
// If the endian-ness hasn't been specified, then state it explicitly, because
// Windows and Unix will use different defaults on the same architecture.
if (!isset($endianness))
{
$endianness = System::getEndianness();
}
if (function_exists('iconv'))
{
$result = $this->toUtf16Iconv($endianness);
}
else
{
$result = $this->toUtf16NoIconv($endianness);
}
if ($strip_bom)
{
$result = self::stripBom($result);
}
return $result;
}
/**
* Strip off the BOM if there is one
*/
private static function stripBom(string $string) : string
{
$bom = pack('H*','FEFF');
return preg_replace("/^$bom/", '', $string);
}
/**
* Converts to UTF-16 using iconv()
*/
private function toUtf16Iconv(int $endianness) : string
{
$in_charset = 'UTF-8';
$out_charset = 'UTF-16';
switch ($endianness)
{
case System::BIG_ENDIAN:
$out_charset .= 'BE';
break;
case System::LITTLE_ENDIAN:
$out_charset .= 'LE';
break;
default:
throw new InvalidArgumentException("Unknown endianness '$endianness'");
break;
}
$result = iconv($in_charset, $out_charset, $this->string);
if ($result === false)
{
throw new RuntimeException("iconv() failed converting from '$in_charset' to '$out_charset'");
}
return $result;
}
/**
* Converts to UTF-16 without using iconv()
*/
private function toUtf16NoIconv(int $endianness) : string
{
$result = '';
$chars = $this->toArray();
foreach ($chars as $char)
{
$result .= (new Utf8Char($char))->toUtf16($endianness);
}
return $result;
}
/**
* Get the rest of the UTF-8 characters, preserving the current key
*/
private function getRemainingData() : void
{
// Get the rest of the characters if we haven't already got them
if (!$this->have_all_chars)
{
// Preserve the variables
$vars = ['byte_index', 'char_index', 'next_char_length'];
$old = [];
foreach ($vars as $var)
{
$old[$var] = $this->$var;
}
while ($this->valid())
{
$this->next();
}
// Restore the indices
foreach ($vars as $var)
{
$this->$var = $old[$var];
}
}
}
/**
* Gets the length in bytes of the next UTF-8 char.
*
* @return int|null returns zero if there isn't a next char
*/
private function nextCharLength() : ?int
{
// TODO: can this ever return null, or does it always return an int?
$i = $this->byte_index;
if (isset($this->string[$i]))
{
if (ord($this->string[$i]) < 0xc0)
{
$i++;
}
else
{
$i++;
while (isset($this->string[$i]) && ((ord($this->string[$i]) & 0xc0) == 0x80))
{
$i++;
}
}
}
return $i - $this->byte_index;
}
private function nextChar() : string
{
$result = '';
for ($i=0, $byte_index = $this->byte_index; $i<$this->next_char_length; $i++, $byte_index++)
{
$result[$i] = $this->string[$byte_index];
}
return $result;
}
private function setNextChar() : void
{
if (!$this->next_char_length)
{
$this->have_all_chars = true;
}
elseif (!isset($this->data[$this->char_index]))
{
$this->data[$this->char_index] = $this->nextChar();
}
}
}