瀏覽代碼

текущее состояние

ilg2005 3 月之前
父節點
當前提交
f75671038c
共有 1 個文件被更改,包括 85 次插入44 次删除
  1. 85 44
      app/Casts/BinaryTextCast.php

+ 85 - 44
app/Casts/BinaryTextCast.php

@@ -31,16 +31,49 @@ class BinaryTextCast implements CastsAttributes
             return $value;
         }
 
+        $firstByte = ord($value[0]);
+        if ($firstByte === 0) {
+            return $this->ensureUtf8(substr($value, 1));
+        }
+
+        if ($firstByte === 1) {
+            $payload = substr($value, 1);
+            $decoded = base64_decode($payload, true);
+            return $this->ensureUtf8($decoded !== false ? $decoded : $payload);
+        }
+
         $decoded = $value;
+        $hadDecode = false;
+
         for ($i = 0; $i < 2; $i++) {
             $tmp = base64_decode($decoded, true);
             if ($tmp === false) {
                 break;
             }
+
+            $hadDecode = true;
             $decoded = $this->stripNulls($tmp);
+            if ($decoded === null || $decoded === '') {
+                break;
+            }
+
+            if (!$this->isBase64($decoded)) {
+                break;
+            }
         }
 
-        return $decoded === null ? null : $this->ensureUtf8($decoded);
+        if ($hadDecode && is_string($decoded)) {
+            return $this->ensureUtf8($decoded);
+        }
+
+        if ($this->isBase64($value)) {
+            $single = base64_decode($value, true);
+            if ($single !== false) {
+                return $this->ensureUtf8($single);
+            }
+        }
+
+        return $this->ensureUtf8($value);
     }
 
     /**
@@ -59,42 +92,66 @@ class BinaryTextCast implements CastsAttributes
 
         // Определяем максимально допустимую длину VARBINARY для данной колонки
         $maxLen = $this->getVarbinaryMaxLength($model, $key);
+        $raw = $this->prepareRawPayload($value);
 
-        // Готовим "сырой" CP1251 (или то, что пришло в base64) и итоговый двойной base64
-        $raw = null;
-        if ($this->isBase64($value)) {
-            $inner = base64_decode($value, true);
-            if ($inner !== false && $this->isBase64($inner)) {
-                // Значение уже двойной base64: пробуем восстановить сырой текст
-                $raw = base64_decode($inner, true);
-                $encoded = $value; // пока используем исходный двойной base64
-            } else {
-                // Только один слой base64 → сырой текст после одного декодирования
-                $raw = $inner !== false ? $inner : null;
-                $encoded = base64_encode($value);
+        $candidates = [
+            base64_encode(base64_encode($raw)),
+            "\x01" . base64_encode($raw),
+            "\x00" . $raw,
+        ];
+
+        $payload = null;
+
+        foreach ($candidates as $candidate) {
+            if (!is_int($maxLen) || strlen($candidate) <= $maxLen) {
+                $payload = $candidate;
+                break;
             }
         }
 
-        if ($raw === null) {
-            // Готовим сырой CP1251 из UTF-8
-            $prepared = function_exists('iconv') ? @iconv('UTF-8', 'CP1251//IGNORE', $value) : $value;
-            if ($prepared === false) {
-                $prepared = $value;
+        if ($payload === null) {
+            if (!is_int($maxLen) || $maxLen <= 0) {
+                return DB::raw('0x');
+            }
+
+            $allowedRaw = max(0, $maxLen - 1);
+            $raw = substr($raw, 0, $allowedRaw);
+            $payload = "\x00" . $raw;
+        }
+
+        return DB::raw('0x' . bin2hex($payload));
+    }
+
+    private function prepareRawPayload(string $value): string
+    {
+        if ($value === '') {
+            return '';
+        }
+
+        if ($this->isBase64($value)) {
+            $first = base64_decode($value, true);
+            if (is_string($first)) {
+                $first = $this->stripNulls($first) ?? '';
+                if ($first !== '' && $this->isBase64($first)) {
+                    $second = base64_decode($first, true);
+                    if (is_string($second)) {
+                        $second = $this->stripNulls($second) ?? '';
+                        return $second;
+                    }
+                }
+
+                return $first;
             }
-            $raw = $prepared;
-            $encoded = base64_encode(base64_encode($prepared));
         }
 
-        // Если известна максимальная длина и результат не помещается,
-        // пересчитываем кодирование для усечения по сырой длине так, чтобы двойной base64 гарантированно влез
-        if (is_int($maxLen) && strlen($encoded) > $maxLen) {
-            $maxRaw = $this->maxRawLenForDoubleBase64($maxLen);
-            $raw = substr($raw, 0, max(0, $maxRaw));
-            $encoded = base64_encode(base64_encode($raw));
+        if (function_exists('iconv')) {
+            $converted = @iconv('UTF-8', 'CP1251//IGNORE', $value);
+            if (is_string($converted)) {
+                return $converted;
+            }
         }
 
-        // MSSQL VARBINARY требует явного бинарного литерала
-        return DB::raw('0x' . bin2hex($encoded));
+        return $value;
     }
 
     /**
@@ -212,20 +269,4 @@ class BinaryTextCast implements CastsAttributes
         return null;
     }
 
-    /**
-     * Вычисляет максимально допустимую длину "сырого" текста (в байтах),
-     * чтобы длина двойного base64 не превышала $maxLen.
-     */
-    private function maxRawLenForDoubleBase64(int $maxLen): int
-    {
-        // Итеративный безопасный расчёт
-        for ($n = $maxLen; $n >= 0; $n--) {
-            $b1 = 4 * (int) ceil($n / 3);
-            $b2 = 4 * (int) ceil($b1 / 3);
-            if ($b2 <= $maxLen) {
-                return $n;
-            }
-        }
-        return 0;
-    }
 }