增加解码宽松模式
This commit is contained in:
parent
f3062b422e
commit
1d3db9278d
@ -35,7 +35,9 @@ func main() {
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
root, err := asar.Decode(file)
|
||||
root, err := asar.DecodeWithOptions(file, asar.DecodeOptions{
|
||||
StrictValidation: false,
|
||||
})
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "asar: %s\n", err)
|
||||
os.Exit(1)
|
||||
|
||||
44
decoder.go
44
decoder.go
@ -3,6 +3,7 @@ package asar // import "b612.me/asar"
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
@ -10,11 +11,27 @@ var (
|
||||
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.
|
||||
//
|
||||
// Returns the root element and nil on success. nil and an error is returned on
|
||||
// failure.
|
||||
func Decode(ra io.ReaderAt) (*Entry, error) {
|
||||
return DecodeWithOptions(ra, DefaultDecodeOptions)
|
||||
}
|
||||
|
||||
// DecodeWithOptions 带选项的解码函数
|
||||
func DecodeWithOptions(ra io.ReaderAt, opts DecodeOptions) (*Entry, error) {
|
||||
headerSize := uint32(0)
|
||||
headerStringSize := uint32(0)
|
||||
|
||||
@ -43,19 +60,42 @@ func Decode(ra io.ReaderAt) (*Entry, error) {
|
||||
}
|
||||
|
||||
headerObjectSize := binary.LittleEndian.Uint32(buff[:4])
|
||||
|
||||
// 在宽松模式下放宽验证
|
||||
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])
|
||||
}
|
||||
|
||||
// 在宽松模式下,尝试找到实际的 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
|
||||
headerSection := io.NewSectionReader(ra, 8+8, int64(headerStringSize))
|
||||
headerSection := io.NewSectionReader(ra, 8+8, actualHeaderSize)
|
||||
baseOffset := 8 + int64(headerSize)
|
||||
baseOffset += baseOffset % 4 // pickle objects are uint32 aligned
|
||||
|
||||
root, err := decodeHeader(ra, headerSection, baseOffset)
|
||||
root, err := decodeHeaderWithOptions(ra, headerSection, baseOffset, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
54
header.go
54
header.go
@ -15,6 +15,7 @@ type jsonReader struct {
|
||||
BaseOffset int64
|
||||
D *json.Decoder
|
||||
Token json.Token
|
||||
Options DecodeOptions // 新增字段
|
||||
}
|
||||
|
||||
func (j *jsonReader) Peek() json.Token {
|
||||
@ -128,9 +129,26 @@ func parseRoot(r *jsonReader) *Entry {
|
||||
r.ExpectStringVal("files")
|
||||
parseFiles(r, entry)
|
||||
r.ExpectDelim('}')
|
||||
|
||||
// 关键修改:根据选项决定是否检查剩余内容
|
||||
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
|
||||
}
|
||||
|
||||
@ -159,7 +177,8 @@ func parseEntry(r *jsonReader, parent *Entry) {
|
||||
}
|
||||
|
||||
for !r.HasDelimRune('}') {
|
||||
switch r.ExpectString() {
|
||||
key := r.ExpectString()
|
||||
switch key {
|
||||
case "files":
|
||||
child.Flags |= FlagDir
|
||||
parseFiles(r, child)
|
||||
@ -176,7 +195,18 @@ func parseEntry(r *jsonReader, parent *Entry) {
|
||||
child.Flags |= FlagExecutable
|
||||
}
|
||||
default:
|
||||
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('}')
|
||||
}
|
||||
|
||||
// 保留原有的 decodeHeader
|
||||
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.UseNumber()
|
||||
reader := jsonReader{
|
||||
ASAR: asar,
|
||||
BaseOffset: offset,
|
||||
D: decoder,
|
||||
Options: opts, // 传递选项
|
||||
}
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
@ -205,8 +242,21 @@ func decodeHeader(asar io.ReaderAt, header *io.SectionReader, offset int64) (ent
|
||||
panic(r)
|
||||
}
|
||||
}
|
||||
|
||||
}()
|
||||
entry = parseRoot(&reader)
|
||||
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)
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user