git subrepo commit (merge) mailcow/src/mailcow-dockerized

subrepo: subdir:   "mailcow/src/mailcow-dockerized"
  merged:   "02ae5285"
upstream: origin:   "https://github.com/mailcow/mailcow-dockerized.git"
  branch:   "master"
  commit:   "649a5c01"
git-subrepo: version:  "0.4.3"
  origin:   "???"
  commit:   "???"
Change-Id: I870ad468fba026cc5abf3c5699ed1e12ff28b32b
diff --git a/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Attributes/TSProperty.php b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Attributes/TSProperty.php
new file mode 100644
index 0000000..ad56aa1
--- /dev/null
+++ b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Attributes/TSProperty.php
@@ -0,0 +1,396 @@
+<?php
+
+namespace LdapRecord\Models\Attributes;
+
+class TSProperty
+{
+    /**
+     * Nibble control values. The first value for each is if the nibble is <= 9, otherwise the second value is used.
+     */
+    const NIBBLE_CONTROL = [
+        'X' => ['001011', '011010'],
+        'Y' => ['001110', '011010'],
+    ];
+
+    /**
+     * The nibble header.
+     */
+    const NIBBLE_HEADER = '1110';
+
+    /**
+     * Conversion factor needed for time values in the TSPropertyArray (stored in microseconds).
+     */
+    const TIME_CONVERSION = 60 * 1000;
+
+    /**
+     * A simple map to help determine how the property needs to be decoded/encoded from/to its binary value.
+     *
+     * There are some names that are simple repeats but have 'W' at the end. Not sure as to what that signifies. I
+     * cannot find any information on them in Microsoft documentation. However, their values appear to stay in sync with
+     * their non 'W' counterparts. But not doing so when manipulating the data manually does not seem to affect anything.
+     * This probably needs more investigation.
+     *
+     * @var array
+     */
+    protected $propTypes = [
+        'string' => [
+            'CtxWFHomeDir',
+            'CtxWFHomeDirW',
+            'CtxWFHomeDirDrive',
+            'CtxWFHomeDirDriveW',
+            'CtxInitialProgram',
+            'CtxInitialProgramW',
+            'CtxWFProfilePath',
+            'CtxWFProfilePathW',
+            'CtxWorkDirectory',
+            'CtxWorkDirectoryW',
+            'CtxCallbackNumber',
+        ],
+        'time' => [
+            'CtxMaxDisconnectionTime',
+            'CtxMaxConnectionTime',
+            'CtxMaxIdleTime',
+        ],
+        'int' => [
+            'CtxCfgFlags1',
+            'CtxCfgPresent',
+            'CtxKeyboardLayout',
+            'CtxMinEncryptionLevel',
+            'CtxNWLogonServer',
+            'CtxShadow',
+        ],
+    ];
+
+    /**
+     * The property name.
+     *
+     * @var string
+     */
+    protected $name;
+
+    /**
+     * The property value.
+     *
+     * @var string|int
+     */
+    protected $value;
+
+    /**
+     * The property value type.
+     *
+     * @var int
+     */
+    protected $valueType = 1;
+
+    /**
+     * Pass binary TSProperty data to construct its object representation.
+     *
+     * @param string|null $value
+     */
+    public function __construct($value = null)
+    {
+        if ($value) {
+            $this->decode(bin2hex($value));
+        }
+    }
+
+    /**
+     * Set the name for the TSProperty.
+     *
+     * @param string $name
+     *
+     * @return TSProperty
+     */
+    public function setName($name)
+    {
+        $this->name = $name;
+
+        return $this;
+    }
+
+    /**
+     * Get the name for the TSProperty.
+     *
+     * @return string
+     */
+    public function getName()
+    {
+        return $this->name;
+    }
+
+    /**
+     * Set the value for the TSProperty.
+     *
+     * @param string|int $value
+     *
+     * @return TSProperty
+     */
+    public function setValue($value)
+    {
+        $this->value = $value;
+
+        return $this;
+    }
+
+    /**
+     * Get the value for the TSProperty.
+     *
+     * @return string|int
+     */
+    public function getValue()
+    {
+        return $this->value;
+    }
+
+    /**
+     * Convert the TSProperty name/value back to its binary
+     * representation for the userParameters blob.
+     *
+     * @return string
+     */
+    public function toBinary()
+    {
+        $name = bin2hex($this->name);
+
+        $binValue = $this->getEncodedValueForProp($this->name, $this->value);
+
+        $valueLen = strlen(bin2hex($binValue)) / 3;
+
+        $binary = hex2bin(
+            $this->dec2hex(strlen($name))
+            .$this->dec2hex($valueLen)
+            .$this->dec2hex($this->valueType)
+            .$name
+        );
+
+        return $binary.$binValue;
+    }
+
+    /**
+     * Given a TSProperty blob, decode the name/value/type/etc.
+     *
+     * @param string $tsProperty
+     */
+    protected function decode($tsProperty)
+    {
+        $nameLength = hexdec(substr($tsProperty, 0, 2));
+
+        // 1 data byte is 3 encoded bytes
+        $valueLength = hexdec(substr($tsProperty, 2, 2)) * 3;
+
+        $this->valueType = hexdec(substr($tsProperty, 4, 2));
+        $this->name = pack('H*', substr($tsProperty, 6, $nameLength));
+        $this->value = $this->getDecodedValueForProp($this->name, substr($tsProperty, 6 + $nameLength, $valueLength));
+    }
+
+    /**
+     * Based on the property name/value in question, get its encoded form.
+     *
+     * @param string     $propName
+     * @param string|int $propValue
+     *
+     * @return string
+     */
+    protected function getEncodedValueForProp($propName, $propValue)
+    {
+        if (in_array($propName, $this->propTypes['string'])) {
+            // Simple strings are null terminated. Unsure if this is
+            // needed or simply a product of how ADUC does stuff?
+            $value = $this->encodePropValue($propValue."\0", true);
+        } elseif (in_array($propName, $this->propTypes['time'])) {
+            // Needs to be in microseconds (assuming it is in minute format)...
+            $value = $this->encodePropValue($propValue * self::TIME_CONVERSION);
+        } else {
+            $value = $this->encodePropValue($propValue);
+        }
+
+        return $value;
+    }
+
+    /**
+     * Based on the property name in question, get its actual value from the binary blob value.
+     *
+     * @param string $propName
+     * @param string $propValue
+     *
+     * @return string|int
+     */
+    protected function getDecodedValueForProp($propName, $propValue)
+    {
+        if (in_array($propName, $this->propTypes['string'])) {
+            // Strip away null terminators. I think this should
+            // be desired, otherwise it just ends in confusion.
+            $value = str_replace("\0", '', $this->decodePropValue($propValue, true));
+        } elseif (in_array($propName, $this->propTypes['time'])) {
+            // Convert from microseconds to minutes (how ADUC displays
+            // it anyway, and seems the most practical).
+            $value = hexdec($this->decodePropValue($propValue)) / self::TIME_CONVERSION;
+        } elseif (in_array($propName, $this->propTypes['int'])) {
+            $value = hexdec($this->decodePropValue($propValue));
+        } else {
+            $value = $this->decodePropValue($propValue);
+        }
+
+        return $value;
+    }
+
+    /**
+     * Decode the property by inspecting the nibbles of each blob, checking
+     * the control, and adding up the results into a final value.
+     *
+     * @param string $hex
+     * @param bool   $string Whether or not this is simple string data.
+     *
+     * @return string
+     */
+    protected function decodePropValue($hex, $string = false)
+    {
+        $decodePropValue = '';
+
+        $blobs = str_split($hex, 6);
+
+        foreach ($blobs as $blob) {
+            $bin = decbin(hexdec($blob));
+
+            $controlY = substr($bin, 4, 6);
+            $nibbleY = substr($bin, 10, 4);
+            $controlX = substr($bin, 14, 6);
+            $nibbleX = substr($bin, 20, 4);
+
+            $byte = $this->nibbleControl($nibbleX, $controlX).$this->nibbleControl($nibbleY, $controlY);
+
+            if ($string) {
+                $decodePropValue .= MbString::chr(bindec($byte));
+            } else {
+                $decodePropValue = $this->dec2hex(bindec($byte)).$decodePropValue;
+            }
+        }
+
+        return $decodePropValue;
+    }
+
+    /**
+     * Get the encoded property value as a binary blob.
+     *
+     * @param string $value
+     * @param bool   $string
+     *
+     * @return string
+     */
+    protected function encodePropValue($value, $string = false)
+    {
+        // An int must be properly padded. (then split and reversed).
+        // For a string, we just split the chars. This seems
+        // to be the easiest way to handle UTF-8 characters
+        // instead of trying to work with their hex values.
+        $chars = $string ? MbString::split($value) : array_reverse(str_split($this->dec2hex($value, 8), 2));
+
+        $encoded = '';
+
+        foreach ($chars as $char) {
+            // Get the bits for the char. Using this method to ensure it is fully padded.
+            $bits = sprintf('%08b', $string ? MbString::ord($char) : hexdec($char));
+            $nibbleX = substr($bits, 0, 4);
+            $nibbleY = substr($bits, 4, 4);
+
+            // Construct the value with the header, high nibble, then low nibble.
+            $value = self::NIBBLE_HEADER;
+
+            foreach (['Y' => $nibbleY, 'X' => $nibbleX] as $nibbleType => $nibble) {
+                $value .= $this->getNibbleWithControl($nibbleType, $nibble);
+            }
+
+            // Convert it back to a binary bit stream
+            foreach ([0, 8, 16] as $start) {
+                $encoded .= $this->packBitString(substr($value, $start, 8), 8);
+            }
+        }
+
+        return $encoded;
+    }
+
+    /**
+     * PHP's pack() function has no 'b' or 'B' template. This is
+     * a workaround that turns a literal bit-string into a
+     * packed byte-string with 8 bits per byte.
+     *
+     * @param string $bits
+     * @param bool   $len
+     *
+     * @return string
+     */
+    protected function packBitString($bits, $len)
+    {
+        $bits = substr($bits, 0, $len);
+        // Pad input with zeros to next multiple of 4 above $len
+        $bits = str_pad($bits, 4 * (int) (($len + 3) / 4), '0');
+
+        // Split input into chunks of 4 bits, convert each to hex and pack them
+        $nibbles = str_split($bits, 4);
+        foreach ($nibbles as $i => $nibble) {
+            $nibbles[$i] = base_convert($nibble, 2, 16);
+        }
+
+        return pack('H*', implode('', $nibbles));
+    }
+
+    /**
+     * Based on the control, adjust the nibble accordingly.
+     *
+     * @param string $nibble
+     * @param string $control
+     *
+     * @return string
+     */
+    protected function nibbleControl($nibble, $control)
+    {
+        // This control stays constant for the low/high nibbles,
+        // so it doesn't matter which we compare to
+        if ($control == self::NIBBLE_CONTROL['X'][1]) {
+            $dec = bindec($nibble);
+            $dec += 9;
+            $nibble = str_pad(decbin($dec), 4, '0', STR_PAD_LEFT);
+        }
+
+        return $nibble;
+    }
+
+    /**
+     * Get the nibble value with the control prefixed.
+     *
+     * If the nibble dec is <= 9, the control X equals 001011 and Y equals 001110, otherwise if the nibble dec is > 9
+     * the control for X or Y equals 011010. Additionally, if the dec value of the nibble is > 9, then the nibble value
+     * must be subtracted by 9 before the final value is constructed.
+     *
+     * @param string $nibbleType Either X or Y
+     * @param string $nibble
+     *
+     * @return string
+     */
+    protected function getNibbleWithControl($nibbleType, $nibble)
+    {
+        $dec = bindec($nibble);
+
+        if ($dec > 9) {
+            $dec -= 9;
+            $control = self::NIBBLE_CONTROL[$nibbleType][1];
+        } else {
+            $control = self::NIBBLE_CONTROL[$nibbleType][0];
+        }
+
+        return $control.sprintf('%04d', decbin($dec));
+    }
+
+    /**
+     * Need to make sure hex values are always an even length, so pad as needed.
+     *
+     * @param int $int
+     * @param int $padLength The hex string must be padded to this length (with zeros).
+     *
+     * @return string
+     */
+    protected function dec2hex($int, $padLength = 2)
+    {
+        return str_pad(dechex($int), $padLength, 0, STR_PAD_LEFT);
+    }
+}