318 lines
7.8 KiB
Go
318 lines
7.8 KiB
Go
package utils
|
||
|
||
import (
|
||
"fmt"
|
||
"strconv"
|
||
"strings"
|
||
)
|
||
|
||
// PHPValue 定义 PHP 序列化值的类型(兼容字符串、整数、数组)
|
||
type PHPValue interface{}
|
||
|
||
// PHPDecode 原生解析 PHP 序列化字符串为 map[string]PHPValue
|
||
func PHPDecode(serialized string) (map[string]PHPValue, error) {
|
||
// 预处理:去除首尾空白
|
||
serialized = strings.TrimSpace(serialized)
|
||
|
||
// 验证是否为 PHP 数组格式(a:N:{...})
|
||
if !strings.HasPrefix(serialized, "a:") {
|
||
return nil, fmt.Errorf("不是 PHP 数组格式")
|
||
}
|
||
|
||
// 提取数组内容(去掉外层 a:N:{ 和 末尾的 })
|
||
colonIdx := strings.Index(serialized, ":")
|
||
if colonIdx == -1 {
|
||
return nil, fmt.Errorf("格式错误:缺少冒号")
|
||
}
|
||
braceOpenIdx := strings.Index(serialized, "{")
|
||
if braceOpenIdx == -1 {
|
||
return nil, fmt.Errorf("格式错误:缺少左大括号")
|
||
}
|
||
content := serialized[braceOpenIdx+1 : len(serialized)-1]
|
||
|
||
// 递归解析数组内容
|
||
result := make(map[string]PHPValue)
|
||
idx := 0
|
||
contentLen := len(content)
|
||
|
||
for idx < contentLen {
|
||
// 跳过空白字符
|
||
for idx < contentLen && (content[idx] == ' ' || content[idx] == '\t' || content[idx] == '\n') {
|
||
idx++
|
||
}
|
||
if idx >= contentLen {
|
||
break
|
||
}
|
||
|
||
// 解析 Key
|
||
key, keyEndIdx, err := parsePHPValueAt(content, idx)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("解析 Key 失败:%v", err)
|
||
}
|
||
idx = keyEndIdx + 1 // 跳过 Key 后的分号
|
||
|
||
// 解析 Value
|
||
val, valEndIdx, err := parsePHPValueAt(content, idx)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("解析 Value 失败:%v", err)
|
||
}
|
||
idx = valEndIdx + 1 // 跳过 Value 后的分号
|
||
|
||
// 将 Key 转为字符串并存入结果
|
||
var keyStr string
|
||
switch k := key.(type) {
|
||
case string:
|
||
keyStr = k
|
||
case int:
|
||
keyStr = strconv.Itoa(k)
|
||
default:
|
||
keyStr = fmt.Sprintf("%v", k)
|
||
}
|
||
result[keyStr] = val
|
||
}
|
||
|
||
return result, nil
|
||
}
|
||
|
||
// parsePHPValueAt 从指定索引解析单个 PHP 序列化值
|
||
func parsePHPValueAt(s string, start int) (PHPValue, int, error) {
|
||
if start >= len(s) {
|
||
return nil, -1, fmt.Errorf("索引越界")
|
||
}
|
||
|
||
// 根据类型前缀解析
|
||
switch s[start] {
|
||
case 's': // 字符串类型:s:长度:"内容";
|
||
return parsePHPString(s, start)
|
||
case 'i': // 整数类型:i:数值;
|
||
return parsePHPInt(s, start)
|
||
case 'a': // 数组类型:a:长度:{...};
|
||
return parsePHPArray(s, start)
|
||
default:
|
||
return nil, -1, fmt.Errorf("不支持的类型:%c", s[start])
|
||
}
|
||
}
|
||
|
||
// parsePHPString 解析 PHP 字符串类型(s:len:"value")
|
||
func parsePHPString(s string, start int) (string, int, error) {
|
||
// 格式:s:3:"abc";
|
||
// 1. 跳过 "s:"
|
||
if len(s) < start+2 || s[start:start+2] != "s:" {
|
||
return "", -1, fmt.Errorf("不是字符串类型")
|
||
}
|
||
idx := start + 2
|
||
|
||
// 2. 解析长度
|
||
lenStr := ""
|
||
for idx < len(s) && s[idx] != ':' {
|
||
lenStr += string(s[idx])
|
||
idx++
|
||
}
|
||
if idx >= len(s) || s[idx] != ':' {
|
||
return "", -1, fmt.Errorf("字符串长度解析失败")
|
||
}
|
||
strLen, err := strconv.Atoi(lenStr)
|
||
if err != nil {
|
||
return "", -1, fmt.Errorf("长度不是数字:%v", err)
|
||
}
|
||
idx++ // 跳过冒号
|
||
|
||
// 3. 跳过引号
|
||
if idx >= len(s) || s[idx] != '"' {
|
||
return "", -1, fmt.Errorf("缺少字符串起始引号")
|
||
}
|
||
idx++
|
||
|
||
// 4. 提取字符串内容
|
||
endIdx := idx + strLen
|
||
if endIdx > len(s) {
|
||
return "", -1, fmt.Errorf("字符串长度不足")
|
||
}
|
||
strVal := s[idx:endIdx]
|
||
idx = endIdx
|
||
|
||
// 5. 跳过结束引号和分号
|
||
if idx >= len(s) || s[idx] != '"' {
|
||
return "", -1, fmt.Errorf("缺少字符串结束引号")
|
||
}
|
||
idx++
|
||
if idx >= len(s) || s[idx] != ';' {
|
||
return "", -1, fmt.Errorf("缺少字符串结束分号")
|
||
}
|
||
|
||
return strVal, idx, nil
|
||
}
|
||
|
||
// parsePHPInt 解析 PHP 整数类型(i:123;)
|
||
func parsePHPInt(s string, start int) (int, int, error) {
|
||
// 格式:i:123;
|
||
// 1. 跳过 "i:"
|
||
if len(s) < start+2 || s[start:start+2] != "i:" {
|
||
return 0, -1, fmt.Errorf("不是整数类型")
|
||
}
|
||
idx := start + 2
|
||
|
||
// 2. 解析数字
|
||
numStr := ""
|
||
for idx < len(s) && s[idx] != ';' {
|
||
numStr += string(s[idx])
|
||
idx++
|
||
}
|
||
if numStr == "" {
|
||
return 0, -1, fmt.Errorf("整数为空")
|
||
}
|
||
numVal, err := strconv.Atoi(numStr)
|
||
if err != nil {
|
||
return 0, -1, fmt.Errorf("整数解析失败:%v", err)
|
||
}
|
||
|
||
return numVal, idx, nil
|
||
}
|
||
|
||
// parsePHPArray 解析 PHP 数组类型(a:len:{...})
|
||
func parsePHPArray(s string, start int) (map[string]PHPValue, int, error) {
|
||
// 格式:a:2:{s:1:"k";i:123;s:1:"v";s:3:"abc";};
|
||
// 1. 跳过 "a:"
|
||
if len(s) < start+2 || s[start:start+2] != "a:" {
|
||
return nil, -1, fmt.Errorf("不是数组类型")
|
||
}
|
||
idx := start + 2
|
||
|
||
// 2. 解析数组长度(仅校验,实际按内容解析)
|
||
for idx < len(s) && s[idx] != ':' {
|
||
idx++
|
||
}
|
||
if idx >= len(s) || s[idx] != ':' {
|
||
return nil, -1, fmt.Errorf("数组长度解析失败")
|
||
}
|
||
idx++
|
||
|
||
// 3. 找到数组起始大括号
|
||
if idx >= len(s) || s[idx] != '{' {
|
||
return nil, -1, fmt.Errorf("缺少数组起始大括号")
|
||
}
|
||
idx++
|
||
|
||
// 4. 解析数组内容(递归调用 PHPDecode 的核心逻辑)
|
||
arrayContentStart := idx
|
||
depth := 1
|
||
arrayContentEnd := -1
|
||
|
||
// 找到匹配的结束大括号(处理嵌套数组)
|
||
for idx < len(s) && depth > 0 {
|
||
switch s[idx] {
|
||
case '{':
|
||
depth++
|
||
case '}':
|
||
depth--
|
||
if depth == 0 {
|
||
arrayContentEnd = idx
|
||
}
|
||
}
|
||
idx++
|
||
}
|
||
|
||
if arrayContentEnd == -1 {
|
||
return nil, -1, fmt.Errorf("数组缺少结束大括号")
|
||
}
|
||
|
||
// 5. 解析数组内容为 map
|
||
arrayContent := s[arrayContentStart:arrayContentEnd]
|
||
arrayResult := make(map[string]PHPValue)
|
||
innerIdx := 0
|
||
contentLen := len(arrayContent)
|
||
|
||
for innerIdx < contentLen {
|
||
// 跳过空白
|
||
for innerIdx < contentLen && (arrayContent[innerIdx] == ' ' || arrayContent[innerIdx] == '\t') {
|
||
innerIdx++
|
||
}
|
||
if innerIdx >= contentLen {
|
||
break
|
||
}
|
||
|
||
// 解析 Key
|
||
key, keyEnd, err := parsePHPValueAt(arrayContent, innerIdx)
|
||
if err != nil {
|
||
return nil, -1, fmt.Errorf("解析数组 Key 失败:%v", err)
|
||
}
|
||
innerIdx = keyEnd + 1 // 跳过分号
|
||
|
||
// 解析 Value
|
||
val, valEnd, err := parsePHPValueAt(arrayContent, innerIdx)
|
||
if err != nil {
|
||
return nil, -1, fmt.Errorf("解析数组 Value 失败:%v", err)
|
||
}
|
||
innerIdx = valEnd + 1 // 跳过分号
|
||
|
||
// 转换 Key 为字符串
|
||
var keyStr string
|
||
switch k := key.(type) {
|
||
case string:
|
||
keyStr = k
|
||
case int:
|
||
keyStr = strconv.Itoa(k)
|
||
default:
|
||
keyStr = fmt.Sprintf("%v", k)
|
||
}
|
||
arrayResult[keyStr] = val
|
||
}
|
||
|
||
// 6. 跳过数组结束后的分号
|
||
if idx < len(s) && s[idx] == ';' {
|
||
idx++
|
||
}
|
||
|
||
return arrayResult, idx - 1, nil // 返回数组结束的索引
|
||
}
|
||
|
||
// GetInt 从 map 中安全获取 int 类型值,失败返回默认值
|
||
func GetInt(m map[string]PHPValue, key string, defaultValue int) int {
|
||
val, ok := m[key]
|
||
if !ok {
|
||
return defaultValue
|
||
}
|
||
// 类型断言:将 interface{} 转为 int
|
||
num, ok := val.(int)
|
||
if !ok {
|
||
fmt.Printf("警告:%s 不是 int 类型,使用默认值 %d\n", key, defaultValue)
|
||
return defaultValue
|
||
}
|
||
return num
|
||
}
|
||
|
||
// GetString 从 map 中安全获取 string 类型值,失败返回默认值
|
||
func GetString(m map[string]PHPValue, key string, defaultValue string) string {
|
||
val, ok := m[key]
|
||
if !ok {
|
||
return defaultValue
|
||
}
|
||
// 类型断言:将 interface{} 转为 string
|
||
str, ok := val.(string)
|
||
if !ok {
|
||
fmt.Printf("警告:%s 不是 string 类型,使用默认值 %s\n", key, defaultValue)
|
||
return defaultValue
|
||
}
|
||
return str
|
||
}
|
||
|
||
// GetMap 从 map 中安全获取嵌套 map 类型值,失败返回空 map
|
||
func GetMap(m map[string]PHPValue, key string) map[string]interface{} {
|
||
val, ok := m[key]
|
||
if !ok {
|
||
return make(map[string]interface{})
|
||
}
|
||
// 类型断言:将 interface{} 转为 map[string]interface{}
|
||
nestedMap, ok := val.(map[string]PHPValue)
|
||
if !ok {
|
||
fmt.Printf("警告:%s 不是数组类型\n", key)
|
||
return make(map[string]interface{})
|
||
}
|
||
// 转换为通用 map[string]interface{}
|
||
result := make(map[string]interface{})
|
||
for k, v := range nestedMap {
|
||
result[k] = v
|
||
}
|
||
return result
|
||
}
|