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) }