441 lines
10 KiB
Go
441 lines
10 KiB
Go
package buffer
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/base64"
|
|
"encoding/hex"
|
|
"reflect"
|
|
"strconv"
|
|
|
|
"apigo.cc/ai/ai/goja"
|
|
"apigo.cc/ai/ai/goja_nodejs/errors"
|
|
"apigo.cc/ai/ai/goja_nodejs/require"
|
|
|
|
"github.com/dop251/base64dec"
|
|
"golang.org/x/text/encoding/unicode"
|
|
)
|
|
|
|
const ModuleName = "buffer"
|
|
|
|
type Buffer struct {
|
|
r *goja.Runtime
|
|
|
|
bufferCtorObj *goja.Object
|
|
|
|
uint8ArrayCtorObj *goja.Object
|
|
uint8ArrayCtor goja.Constructor
|
|
}
|
|
|
|
var (
|
|
symApi = goja.NewSymbol("api")
|
|
)
|
|
|
|
var (
|
|
reflectTypeArrayBuffer = reflect.TypeOf(goja.ArrayBuffer{})
|
|
reflectTypeString = reflect.TypeOf("")
|
|
reflectTypeInt = reflect.TypeOf(int64(0))
|
|
reflectTypeFloat = reflect.TypeOf(0.0)
|
|
reflectTypeBytes = reflect.TypeOf(([]byte)(nil))
|
|
)
|
|
|
|
func Enable(runtime *goja.Runtime) {
|
|
runtime.Set("Buffer", require.Require(runtime, ModuleName).ToObject(runtime).Get("Buffer"))
|
|
}
|
|
|
|
func Bytes(r *goja.Runtime, v goja.Value) []byte {
|
|
var b []byte
|
|
err := r.ExportTo(v, &b)
|
|
if err != nil {
|
|
return []byte(v.String())
|
|
}
|
|
return b
|
|
}
|
|
|
|
func mod(r *goja.Runtime) *goja.Object {
|
|
res := r.Get("Buffer")
|
|
if res == nil {
|
|
res = require.Require(r, ModuleName).ToObject(r).Get("Buffer")
|
|
}
|
|
m, ok := res.(*goja.Object)
|
|
if !ok {
|
|
panic(r.NewTypeError("Could not extract Buffer"))
|
|
}
|
|
return m
|
|
}
|
|
|
|
func api(mod *goja.Object) *Buffer {
|
|
if s := mod.GetSymbol(symApi); s != nil {
|
|
b, _ := s.Export().(*Buffer)
|
|
return b
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func GetApi(r *goja.Runtime) *Buffer {
|
|
return api(mod(r))
|
|
}
|
|
|
|
func DecodeBytes(r *goja.Runtime, arg, enc goja.Value) []byte {
|
|
switch arg.ExportType() {
|
|
case reflectTypeArrayBuffer:
|
|
return arg.Export().(goja.ArrayBuffer).Bytes()
|
|
case reflectTypeString:
|
|
var codec StringCodec
|
|
if !goja.IsUndefined(enc) {
|
|
codec = stringCodecs[enc.String()]
|
|
}
|
|
if codec == nil {
|
|
codec = utf8Codec
|
|
}
|
|
return codec.DecodeAppend(arg.String(), nil)
|
|
default:
|
|
if o, ok := arg.(*goja.Object); ok {
|
|
if o.ExportType() == reflectTypeBytes {
|
|
return o.Export().([]byte)
|
|
}
|
|
}
|
|
}
|
|
panic(errors.NewTypeError(r, errors.ErrCodeInvalidArgType, "The \"data\" argument must be of type string or an instance of Buffer, TypedArray, or DataView."))
|
|
}
|
|
|
|
func WrapBytes(r *goja.Runtime, data []byte) *goja.Object {
|
|
m := mod(r)
|
|
if api := api(m); api != nil {
|
|
return api.WrapBytes(data)
|
|
}
|
|
if from, ok := goja.AssertFunction(m.Get("from")); ok {
|
|
ab := r.NewArrayBuffer(data)
|
|
v, err := from(m, r.ToValue(ab))
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return v.ToObject(r)
|
|
}
|
|
panic(r.NewTypeError("Buffer.from is not a function"))
|
|
}
|
|
|
|
// EncodeBytes returns the given byte slice encoded as string with the given encoding. If encoding
|
|
// is not specified or not supported, returns a Buffer that wraps the data.
|
|
func EncodeBytes(r *goja.Runtime, data []byte, enc goja.Value) goja.Value {
|
|
var codec StringCodec
|
|
if !goja.IsUndefined(enc) {
|
|
codec = StringCodecByName(enc.String())
|
|
}
|
|
if codec != nil {
|
|
return r.ToValue(codec.Encode(data))
|
|
}
|
|
return WrapBytes(r, data)
|
|
}
|
|
|
|
func (b *Buffer) WrapBytes(data []byte) *goja.Object {
|
|
return b.fromBytes(data)
|
|
}
|
|
|
|
func (b *Buffer) ctor(call goja.ConstructorCall) (res *goja.Object) {
|
|
arg := call.Argument(0)
|
|
switch arg.ExportType() {
|
|
case reflectTypeInt, reflectTypeFloat:
|
|
panic(b.r.NewTypeError("Calling the Buffer constructor with numeric argument is not implemented yet"))
|
|
// TODO implement
|
|
}
|
|
return b._from(call.Arguments...)
|
|
}
|
|
|
|
type StringCodec interface {
|
|
DecodeAppend(string, []byte) []byte
|
|
Encode([]byte) string
|
|
}
|
|
|
|
type hexCodec struct{}
|
|
|
|
func (hexCodec) DecodeAppend(s string, b []byte) []byte {
|
|
l := hex.DecodedLen(len(s))
|
|
dst, res := expandSlice(b, l)
|
|
n, err := hex.Decode(dst, []byte(s))
|
|
if err != nil {
|
|
res = res[:len(b)+n]
|
|
}
|
|
return res
|
|
}
|
|
|
|
func (hexCodec) Encode(b []byte) string {
|
|
return hex.EncodeToString(b)
|
|
}
|
|
|
|
type _utf8Codec struct{}
|
|
|
|
func (_utf8Codec) DecodeAppend(s string, b []byte) []byte {
|
|
r, _ := unicode.UTF8.NewEncoder().String(s)
|
|
dst, res := expandSlice(b, len(r))
|
|
copy(dst, r)
|
|
return res
|
|
}
|
|
|
|
func (_utf8Codec) Encode(b []byte) string {
|
|
r, _ := unicode.UTF8.NewDecoder().Bytes(b)
|
|
return string(r)
|
|
}
|
|
|
|
type base64Codec struct{}
|
|
|
|
type base64UrlCodec struct {
|
|
base64Codec
|
|
}
|
|
|
|
func (base64Codec) DecodeAppend(s string, b []byte) []byte {
|
|
res, _ := Base64DecodeAppend(b, s)
|
|
return res
|
|
}
|
|
|
|
func (base64Codec) Encode(b []byte) string {
|
|
return base64.StdEncoding.EncodeToString(b)
|
|
}
|
|
|
|
func (base64UrlCodec) Encode(b []byte) string {
|
|
return base64.RawURLEncoding.EncodeToString(b)
|
|
}
|
|
|
|
var utf8Codec StringCodec = _utf8Codec{}
|
|
|
|
var stringCodecs = map[string]StringCodec{
|
|
"hex": hexCodec{},
|
|
"utf8": utf8Codec,
|
|
"utf-8": utf8Codec,
|
|
"base64": base64Codec{},
|
|
"base64Url": base64UrlCodec{},
|
|
}
|
|
|
|
func expandSlice(b []byte, l int) (dst, res []byte) {
|
|
if cap(b)-len(b) < l {
|
|
b1 := make([]byte, len(b)+l)
|
|
copy(b1, b)
|
|
dst = b1[len(b):]
|
|
res = b1
|
|
} else {
|
|
dst = b[len(b) : len(b)+l]
|
|
res = b[:len(b)+l]
|
|
}
|
|
return
|
|
}
|
|
|
|
func Base64DecodeAppend(dst []byte, src string) ([]byte, error) {
|
|
l := base64.RawStdEncoding.DecodedLen(len(src))
|
|
d, res := expandSlice(dst, l)
|
|
n, err := base64dec.DecodeBase64(d, src)
|
|
|
|
res = res[:len(dst)+n]
|
|
return res, err
|
|
}
|
|
|
|
func (b *Buffer) fromString(str, enc string) *goja.Object {
|
|
codec := stringCodecs[enc]
|
|
if codec == nil {
|
|
codec = utf8Codec
|
|
}
|
|
return b.fromBytes(codec.DecodeAppend(str, nil))
|
|
}
|
|
|
|
func (b *Buffer) fromBytes(data []byte) *goja.Object {
|
|
o, err := b.uint8ArrayCtor(b.bufferCtorObj, b.r.ToValue(b.r.NewArrayBuffer(data)))
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return o
|
|
}
|
|
|
|
func (b *Buffer) _from(args ...goja.Value) *goja.Object {
|
|
if len(args) == 0 {
|
|
panic(errors.NewTypeError(b.r, errors.ErrCodeInvalidArgType, "The first argument must be of type string or an instance of Buffer, ArrayBuffer, or Array or an Array-like Object. Received undefined"))
|
|
}
|
|
arg := args[0]
|
|
switch arg.ExportType() {
|
|
case reflectTypeArrayBuffer:
|
|
v, err := b.uint8ArrayCtor(b.bufferCtorObj, args...)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return v
|
|
case reflectTypeString:
|
|
var enc string
|
|
if len(args) > 1 {
|
|
enc = args[1].String()
|
|
}
|
|
return b.fromString(arg.String(), enc)
|
|
default:
|
|
if o, ok := arg.(*goja.Object); ok {
|
|
if o.ExportType() == reflectTypeBytes {
|
|
bb, _ := o.Export().([]byte)
|
|
a := make([]byte, len(bb))
|
|
copy(a, bb)
|
|
return b.fromBytes(a)
|
|
} else {
|
|
if f, ok := goja.AssertFunction(o.Get("valueOf")); ok {
|
|
valueOf, err := f(o)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
if valueOf != o {
|
|
args[0] = valueOf
|
|
return b._from(args...)
|
|
}
|
|
}
|
|
|
|
if s := o.GetSymbol(goja.SymToPrimitive); s != nil {
|
|
if f, ok := goja.AssertFunction(s); ok {
|
|
str, err := f(o, b.r.ToValue("string"))
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
args[0] = str
|
|
return b._from(args...)
|
|
}
|
|
}
|
|
}
|
|
// array-like
|
|
if v := o.Get("length"); v != nil {
|
|
length := int(v.ToInteger())
|
|
a := make([]byte, length)
|
|
for i := 0; i < length; i++ {
|
|
item := o.Get(strconv.Itoa(i))
|
|
if item != nil {
|
|
a[i] = byte(item.ToInteger())
|
|
}
|
|
}
|
|
return b.fromBytes(a)
|
|
}
|
|
}
|
|
}
|
|
panic(errors.NewTypeError(b.r, errors.ErrCodeInvalidArgType, "The first argument must be of type string or an instance of Buffer, ArrayBuffer, or Array or an Array-like Object. Received %s", arg))
|
|
}
|
|
|
|
func (b *Buffer) from(call goja.FunctionCall) goja.Value {
|
|
return b._from(call.Arguments...)
|
|
}
|
|
|
|
func isNumber(v goja.Value) bool {
|
|
switch v.ExportType() {
|
|
case reflectTypeInt, reflectTypeFloat:
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func isString(v goja.Value) bool {
|
|
return v.ExportType() == reflectTypeString
|
|
}
|
|
|
|
func StringCodecByName(name string) StringCodec {
|
|
return stringCodecs[name]
|
|
}
|
|
|
|
func (b *Buffer) getStringCodec(enc goja.Value) (codec StringCodec) {
|
|
if !goja.IsUndefined(enc) {
|
|
codec = stringCodecs[enc.String()]
|
|
if codec == nil {
|
|
panic(errors.NewTypeError(b.r, "ERR_UNKNOWN_ENCODING", "Unknown encoding: %s", enc))
|
|
}
|
|
} else {
|
|
codec = utf8Codec
|
|
}
|
|
return
|
|
}
|
|
|
|
func (b *Buffer) fill(buf []byte, fill string, enc goja.Value) []byte {
|
|
codec := b.getStringCodec(enc)
|
|
b1 := codec.DecodeAppend(fill, buf[:0])
|
|
if len(b1) > len(buf) {
|
|
return b1[:len(buf)]
|
|
}
|
|
for i := len(b1); i < len(buf); {
|
|
i += copy(buf[i:], buf[:i])
|
|
}
|
|
return buf
|
|
}
|
|
|
|
func (b *Buffer) alloc(call goja.FunctionCall) goja.Value {
|
|
arg0 := call.Argument(0)
|
|
size := -1
|
|
if isNumber(arg0) {
|
|
size = int(arg0.ToInteger())
|
|
}
|
|
if size < 0 {
|
|
panic(errors.NewTypeError(b.r, errors.ErrCodeInvalidArgType, "The \"size\" argument must be of type number."))
|
|
}
|
|
fill := call.Argument(1)
|
|
buf := make([]byte, size)
|
|
if !goja.IsUndefined(fill) {
|
|
if isString(fill) {
|
|
var enc goja.Value
|
|
if a := call.Argument(2); isString(a) {
|
|
enc = a
|
|
} else {
|
|
enc = goja.Undefined()
|
|
}
|
|
buf = b.fill(buf, fill.String(), enc)
|
|
} else {
|
|
fill = fill.ToNumber()
|
|
if !goja.IsNaN(fill) && !goja.IsInfinity(fill) {
|
|
fillByte := byte(fill.ToInteger())
|
|
if fillByte != 0 {
|
|
for i := range buf {
|
|
buf[i] = fillByte
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return b.fromBytes(buf)
|
|
}
|
|
|
|
func (b *Buffer) proto_toString(call goja.FunctionCall) goja.Value {
|
|
bb := Bytes(b.r, call.This)
|
|
codec := b.getStringCodec(call.Argument(0))
|
|
return b.r.ToValue(codec.Encode(bb))
|
|
}
|
|
|
|
func (b *Buffer) proto_equals(call goja.FunctionCall) goja.Value {
|
|
bb := Bytes(b.r, call.This)
|
|
other := call.Argument(0)
|
|
if b.r.InstanceOf(other, b.uint8ArrayCtorObj) {
|
|
otherBytes := Bytes(b.r, other)
|
|
return b.r.ToValue(bytes.Equal(bb, otherBytes))
|
|
}
|
|
panic(errors.NewTypeError(b.r, errors.ErrCodeInvalidArgType, "The \"otherBuffer\" argument must be an instance of Buffer or Uint8Array."))
|
|
}
|
|
|
|
func Require(runtime *goja.Runtime, module *goja.Object) {
|
|
b := &Buffer{r: runtime}
|
|
uint8Array := runtime.Get("Uint8Array")
|
|
if c, ok := goja.AssertConstructor(uint8Array); ok {
|
|
b.uint8ArrayCtor = c
|
|
} else {
|
|
panic(runtime.NewTypeError("Uint8Array is not a constructor"))
|
|
}
|
|
uint8ArrayObj := uint8Array.ToObject(runtime)
|
|
|
|
ctor := runtime.ToValue(b.ctor).ToObject(runtime)
|
|
ctor.SetPrototype(uint8ArrayObj)
|
|
ctor.DefineDataPropertySymbol(symApi, runtime.ToValue(b), goja.FLAG_FALSE, goja.FLAG_FALSE, goja.FLAG_FALSE)
|
|
b.bufferCtorObj = ctor
|
|
b.uint8ArrayCtorObj = uint8ArrayObj
|
|
|
|
proto := runtime.NewObject()
|
|
proto.SetPrototype(uint8ArrayObj.Get("prototype").ToObject(runtime))
|
|
proto.DefineDataProperty("constructor", ctor, goja.FLAG_TRUE, goja.FLAG_TRUE, goja.FLAG_FALSE)
|
|
proto.Set("equals", b.proto_equals)
|
|
proto.Set("toString", b.proto_toString)
|
|
|
|
ctor.Set("prototype", proto)
|
|
ctor.Set("poolSize", 8192)
|
|
ctor.Set("from", b.from)
|
|
ctor.Set("alloc", b.alloc)
|
|
|
|
exports := module.Get("exports").(*goja.Object)
|
|
exports.Set("Buffer", ctor)
|
|
}
|
|
|
|
func init() {
|
|
require.RegisterCoreModule(ModuleName, Require)
|
|
}
|