增加解码宽松模式

This commit is contained in:
兔子 2025-10-23 21:50:30 +08:00
parent f3062b422e
commit 1d3db9278d
Signed by: b612
GPG Key ID: 99DD2222B612B612
4 changed files with 186 additions and 10 deletions

View File

@ -35,7 +35,9 @@ func main() {
} }
defer file.Close() defer file.Close()
root, err := asar.Decode(file) root, err := asar.DecodeWithOptions(file, asar.DecodeOptions{
StrictValidation: false,
})
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "asar: %s\n", err) fmt.Fprintf(os.Stderr, "asar: %s\n", err)
os.Exit(1) os.Exit(1)

View File

@ -3,6 +3,7 @@ package asar // import "b612.me/asar"
import ( import (
"encoding/binary" "encoding/binary"
"errors" "errors"
"fmt"
"io" "io"
) )
@ -10,11 +11,27 @@ var (
errMalformed = errors.New("asar: malformed archive") errMalformed = errors.New("asar: malformed archive")
) )
// DecodeOptions 用于控制解码行为
type DecodeOptions struct {
// StrictValidation 如果为 false会忽略一些格式验证错误和尾部填充
StrictValidation bool
}
// DefaultDecodeOptions 默认选项(严格模式)
var DefaultDecodeOptions = DecodeOptions{
StrictValidation: true,
}
// Decode decodes the ASAR archive in ra. // Decode decodes the ASAR archive in ra.
// //
// Returns the root element and nil on success. nil and an error is returned on // Returns the root element and nil on success. nil and an error is returned on
// failure. // failure.
func Decode(ra io.ReaderAt) (*Entry, error) { func Decode(ra io.ReaderAt) (*Entry, error) {
return DecodeWithOptions(ra, DefaultDecodeOptions)
}
// DecodeWithOptions 带选项的解码函数
func DecodeWithOptions(ra io.ReaderAt, opts DecodeOptions) (*Entry, error) {
headerSize := uint32(0) headerSize := uint32(0)
headerStringSize := uint32(0) headerStringSize := uint32(0)
@ -43,19 +60,42 @@ func Decode(ra io.ReaderAt) (*Entry, error) {
} }
headerObjectSize := binary.LittleEndian.Uint32(buff[:4]) headerObjectSize := binary.LittleEndian.Uint32(buff[:4])
if headerObjectSize != headerSize-4 {
return nil, errMalformed // 在宽松模式下放宽验证
if opts.StrictValidation {
if headerObjectSize != headerSize-4 {
return nil, errMalformed
}
} else {
// 宽松模式:允许一定的偏差
if headerObjectSize > headerSize || headerObjectSize < headerSize-20 {
return nil, fmt.Errorf("asar: header object size %d is out of reasonable range (header size: %d)",
headerObjectSize, headerSize)
}
} }
headerStringSize = binary.LittleEndian.Uint32(buff[4:8]) headerStringSize = binary.LittleEndian.Uint32(buff[4:8])
} }
// 在宽松模式下,尝试找到实际的 JSON 结束位置
actualHeaderSize := int64(headerStringSize)
if !opts.StrictValidation && headerStringSize > 0 {
// 读取 header 数据来检测实际的 JSON 结束
tempBuf := make([]byte, headerStringSize)
if n, _ := ra.ReadAt(tempBuf, 16); n > 0 {
// 找到最后一个有效的 JSON 结束位置
if endPos := findJSONEnd(tempBuf); endPos > 0 {
actualHeaderSize = int64(endPos)
}
}
}
// read header string // read header string
headerSection := io.NewSectionReader(ra, 8+8, int64(headerStringSize)) headerSection := io.NewSectionReader(ra, 8+8, actualHeaderSize)
baseOffset := 8 + int64(headerSize) baseOffset := 8 + int64(headerSize)
baseOffset += baseOffset % 4 // pickle objects are uint32 aligned baseOffset += baseOffset % 4 // pickle objects are uint32 aligned
root, err := decodeHeader(ra, headerSection, baseOffset) root, err := decodeHeaderWithOptions(ra, headerSection, baseOffset, opts)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -89,3 +89,87 @@ func TestExtractThis(t *testing.T) {
} }
} }
} }
func TestExtractThisWithOpt(t *testing.T) {
f, err := os.Open("testdata/extractthis.asar")
if err != nil {
t.Fatal(err)
}
defer f.Close()
root, err := DecodeWithOptions(f, DecodeOptions{
StrictValidation: false,
})
if err != nil {
t.Fatal(err)
}
if root.Flags&FlagDir == 0 {
t.Fatal("expecting root directory to have FlagDir")
}
{
f1 := root.Find("dir1", "file1.txt")
if f1 == nil {
t.Fatal("could not find dir1/file1.txt")
}
if f1.Path() != "dir1/file1.txt" {
t.Fatal("unexpected path")
}
body, err := ioutil.ReadAll(f1.Open())
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(body, []byte(`file one.`)) {
t.Fatalf("dir1/file1.txt body is incorrect (got %s)", body)
}
}
{
f2 := root.Find("dir2").Find("file3.txt")
if f2 == nil {
t.Fatal("could not find dir2/file3.txt")
}
s := f2.String()
if s != `123` {
t.Fatalf("dir2/file3.txt body is incorrect (got %s)", s)
}
}
{
empty := root.Find("emptyfile.txt")
if empty == nil {
t.Fatal("could not find emptyfile.txt")
}
if len(empty.Bytes()) != 0 {
t.Fatal("expecting emptyfile.txt to be empty")
}
}
{
var i int
root.Walk(func(_ string, _ os.FileInfo, _ error) error {
i++
return nil
})
if i != 7 {
t.Fatalf("expected to walk over 7 items, got %d", i)
}
}
{
var i int
root.Walk(func(_ string, fi os.FileInfo, _ error) error {
i++
if fi.IsDir() {
return filepath.SkipDir
}
return nil
})
if i != 4 {
t.Fatalf("expected to walk over 4 items, got %d", i)
}
}
}

View File

@ -15,6 +15,7 @@ type jsonReader struct {
BaseOffset int64 BaseOffset int64
D *json.Decoder D *json.Decoder
Token json.Token Token json.Token
Options DecodeOptions // 新增字段
} }
func (j *jsonReader) Peek() json.Token { func (j *jsonReader) Peek() json.Token {
@ -128,9 +129,26 @@ func parseRoot(r *jsonReader) *Entry {
r.ExpectStringVal("files") r.ExpectStringVal("files")
parseFiles(r, entry) parseFiles(r, entry)
r.ExpectDelim('}') r.ExpectDelim('}')
if r.Next() != nil {
panic(errHeader) // 关键修改:根据选项决定是否检查剩余内容
if r.Options.StrictValidation {
// 严格模式:不允许有剩余数据
if r.Next() != nil {
panic(errHeader)
}
} else {
// 宽松模式:消费并忽略剩余的 token
// 这些可能是 NUL 填充或其他无关数据
for {
token := r.Next()
if token == nil {
break
}
// 可选:如果需要,可以在这里记录日志
// log.Printf("Ignoring trailing token in ASAR header: %v", token)
}
} }
return entry return entry
} }
@ -159,7 +177,8 @@ func parseEntry(r *jsonReader, parent *Entry) {
} }
for !r.HasDelimRune('}') { for !r.HasDelimRune('}') {
switch r.ExpectString() { key := r.ExpectString()
switch key {
case "files": case "files":
child.Flags |= FlagDir child.Flags |= FlagDir
parseFiles(r, child) parseFiles(r, child)
@ -176,7 +195,18 @@ func parseEntry(r *jsonReader, parent *Entry) {
child.Flags |= FlagExecutable child.Flags |= FlagExecutable
} }
default: default:
panic(errHeader) if r.Options.StrictValidation {
// 严格模式:未知字段导致错误
panic(errHeader)
} else {
// 宽松模式:跳过未知字段
// 需要消费这个未知字段的值
if token := r.Next(); token == nil {
panic(errHeader)
}
// 可选:记录日志
// log.Printf("Ignoring unknown field '%s' in ASAR entry", key)
}
} }
} }
@ -189,13 +219,20 @@ func parseEntry(r *jsonReader, parent *Entry) {
r.ExpectDelim('}') r.ExpectDelim('}')
} }
// 保留原有的 decodeHeader
func decodeHeader(asar io.ReaderAt, header *io.SectionReader, offset int64) (entry *Entry, err error) { func decodeHeader(asar io.ReaderAt, header *io.SectionReader, offset int64) (entry *Entry, err error) {
return decodeHeaderWithOptions(asar, header, offset, DefaultDecodeOptions)
}
// 新增带选项的版本
func decodeHeaderWithOptions(asar io.ReaderAt, header *io.SectionReader, offset int64, opts DecodeOptions) (entry *Entry, err error) {
decoder := json.NewDecoder(header) decoder := json.NewDecoder(header)
decoder.UseNumber() decoder.UseNumber()
reader := jsonReader{ reader := jsonReader{
ASAR: asar, ASAR: asar,
BaseOffset: offset, BaseOffset: offset,
D: decoder, D: decoder,
Options: opts, // 传递选项
} }
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
@ -205,8 +242,21 @@ func decodeHeader(asar io.ReaderAt, header *io.SectionReader, offset int64) (ent
panic(r) panic(r)
} }
} }
}() }()
entry = parseRoot(&reader) entry = parseRoot(&reader)
return return
} }
// findJSONEnd 找到 JSON 的实际结束位置(最后一个有意义的字符)
func findJSONEnd(data []byte) int {
// 从后往前找,跳过所有的 NUL 和空白字符
for i := len(data) - 1; i >= 0; i-- {
b := data[i]
// 跳过 NUL 和空白字符
if b != 0 && b != ' ' && b != '\n' && b != '\r' && b != '\t' {
// 找到了非空白字符,这应该是 JSON 的结束
return i + 1
}
}
return len(data)
}