mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-07-28 23:08:59 +02:00
fix(extensions): bound file reads
This commit is contained in:
@@ -719,16 +719,48 @@ func (r *extensionRuntime) fileRead(call goja.FunctionCall) goja.Value {
|
||||
return r.jsError("%s", err.Error())
|
||||
}
|
||||
|
||||
data, err := os.ReadFile(fullPath)
|
||||
file, err := os.Open(fullPath)
|
||||
if err != nil {
|
||||
return r.jsError("%s", err.Error())
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
data, err := io.ReadAll(io.LimitReader(file, maxExtensionFileReadBytes+1))
|
||||
if err != nil {
|
||||
return r.jsError("%s", err.Error())
|
||||
}
|
||||
if int64(len(data)) > maxExtensionFileReadBytes {
|
||||
return r.jsError(extensionFileReadLimitError)
|
||||
}
|
||||
|
||||
return r.jsSuccess(map[string]any{
|
||||
"data": string(data),
|
||||
})
|
||||
}
|
||||
|
||||
const (
|
||||
maxExtensionFileReadBytes = int64(16 << 20)
|
||||
extensionFileReadLimitError = "file read exceeds 16 MiB limit; use file.readBytes with offset and length to read it in chunks"
|
||||
)
|
||||
|
||||
func extensionFileReadLength(size, offset, requested int64) (int64, error) {
|
||||
remaining := size - offset
|
||||
if remaining < 0 {
|
||||
remaining = 0
|
||||
}
|
||||
|
||||
if requested < 0 {
|
||||
if remaining > maxExtensionFileReadBytes {
|
||||
return 0, fmt.Errorf("%s", extensionFileReadLimitError)
|
||||
}
|
||||
return remaining, nil
|
||||
}
|
||||
if requested > maxExtensionFileReadBytes {
|
||||
return 0, fmt.Errorf("%s", extensionFileReadLimitError)
|
||||
}
|
||||
return min(requested, remaining), nil
|
||||
}
|
||||
|
||||
func (r *extensionRuntime) fileReadBytes(call goja.FunctionCall) goja.Value {
|
||||
if len(call.Arguments) < 1 {
|
||||
return r.jsError("path is required")
|
||||
@@ -766,22 +798,17 @@ func (r *extensionRuntime) fileReadBytes(call goja.FunctionCall) goja.Value {
|
||||
return r.jsError("failed to seek file: %v", err)
|
||||
}
|
||||
|
||||
var data []byte
|
||||
switch {
|
||||
case length == 0:
|
||||
data = []byte{}
|
||||
case length > 0:
|
||||
buf := make([]byte, int(length))
|
||||
n, readErr := file.Read(buf)
|
||||
if readErr != nil && readErr != io.EOF {
|
||||
readLength, err := extensionFileReadLength(size, offset, length)
|
||||
if err != nil {
|
||||
return r.jsError("%s", err.Error())
|
||||
}
|
||||
data := make([]byte, int(readLength))
|
||||
if readLength > 0 {
|
||||
n, readErr := io.ReadFull(file, data)
|
||||
if readErr != nil && readErr != io.EOF && readErr != io.ErrUnexpectedEOF {
|
||||
return r.jsError("failed to read file: %v", readErr)
|
||||
}
|
||||
data = buf[:n]
|
||||
default:
|
||||
data, err = io.ReadAll(file)
|
||||
if err != nil {
|
||||
return r.jsError("failed to read file: %v", err)
|
||||
}
|
||||
data = data[:n]
|
||||
}
|
||||
|
||||
if strings.EqualFold(strings.TrimSpace(encoding), "bytes") ||
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
package gobackend
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/dop251/goja"
|
||||
)
|
||||
|
||||
func TestExtensionFileReadLengthRequiresChunking(t *testing.T) {
|
||||
if _, err := extensionFileReadLength(maxExtensionFileReadBytes+1, 0, -1); err == nil {
|
||||
t.Fatal("unbounded large read was accepted")
|
||||
}
|
||||
if _, err := extensionFileReadLength(maxExtensionFileReadBytes+1, 0, maxExtensionFileReadBytes+1); err == nil {
|
||||
t.Fatal("oversized explicit read was accepted")
|
||||
}
|
||||
if got, err := extensionFileReadLength(maxExtensionFileReadBytes+1, maxExtensionFileReadBytes, -1); err != nil || got != 1 {
|
||||
t.Fatalf("tail read length = %d, %v", got, err)
|
||||
}
|
||||
if got, err := extensionFileReadLength(5, 2, 10); err != nil || got != 3 {
|
||||
t.Fatalf("clamped read length = %d, %v", got, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtensionFileAPIsRejectUnboundedLargeReads(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
path := filepath.Join(dir, "large.bin")
|
||||
file, err := os.Create(path)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := file.Truncate(maxExtensionFileReadBytes + 1); err != nil {
|
||||
file.Close()
|
||||
t.Fatal(err)
|
||||
}
|
||||
file.Close()
|
||||
|
||||
vm := goja.New()
|
||||
runtime := &extensionRuntime{
|
||||
vm: vm,
|
||||
dataDir: dir,
|
||||
manifest: &ExtensionManifest{Permissions: ExtensionPermissions{File: true}},
|
||||
}
|
||||
|
||||
readResult := runtime.fileRead(goja.FunctionCall{Arguments: []goja.Value{vm.ToValue("large.bin")}}).Export().(map[string]any)
|
||||
if readResult["success"] != false || !strings.Contains(readResult["error"].(string), "chunks") {
|
||||
t.Fatalf("fileRead result = %#v", readResult)
|
||||
}
|
||||
|
||||
readBytesResult := runtime.fileReadBytes(goja.FunctionCall{Arguments: []goja.Value{vm.ToValue("large.bin")}}).Export().(map[string]any)
|
||||
if readBytesResult["success"] != false || !strings.Contains(readBytesResult["error"].(string), "chunks") {
|
||||
t.Fatalf("fileReadBytes result = %#v", readBytesResult)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user