github.com/iosif02/goja_nodejs@v1.0.1/buffer/buffer.go (about) 1 package buffer 2 3 import ( 4 "bytes" 5 "encoding/base64" 6 "encoding/hex" 7 "reflect" 8 "strconv" 9 10 "github.com/iosif02/goja" 11 "github.com/iosif02/goja_nodejs/errors" 12 "github.com/iosif02/goja_nodejs/require" 13 14 "golang.org/x/text/encoding/unicode" 15 ) 16 17 const ModuleName = "buffer" 18 19 type Buffer struct { 20 r *goja.Runtime 21 22 bufferCtorObj *goja.Object 23 24 uint8ArrayCtorObj *goja.Object 25 uint8ArrayCtor goja.Constructor 26 } 27 28 var ( 29 symApi = goja.NewSymbol("api") 30 ) 31 32 var ( 33 reflectTypeArrayBuffer = reflect.TypeOf(goja.ArrayBuffer{}) 34 reflectTypeString = reflect.TypeOf("") 35 reflectTypeInt = reflect.TypeOf(int64(0)) 36 reflectTypeFloat = reflect.TypeOf(0.0) 37 reflectTypeBytes = reflect.TypeOf(([]byte)(nil)) 38 ) 39 40 func Enable(runtime *goja.Runtime) { 41 runtime.Set("Buffer", require.Require(runtime, ModuleName).ToObject(runtime).Get("Buffer")) 42 } 43 44 func Bytes(r *goja.Runtime, v goja.Value) []byte { 45 var b []byte 46 err := r.ExportTo(v, &b) 47 if err != nil { 48 return []byte(v.String()) 49 } 50 return b 51 } 52 53 func mod(r *goja.Runtime) *goja.Object { 54 res := r.Get("Buffer") 55 if res == nil { 56 res = require.Require(r, ModuleName).ToObject(r).Get("Buffer") 57 } 58 m, ok := res.(*goja.Object) 59 if !ok { 60 panic(r.NewTypeError("Could not extract Buffer")) 61 } 62 return m 63 } 64 65 func api(mod *goja.Object) *Buffer { 66 if s := mod.GetSymbol(symApi); s != nil { 67 b, _ := s.Export().(*Buffer) 68 return b 69 } 70 71 return nil 72 } 73 74 func GetApi(r *goja.Runtime) *Buffer { 75 return api(mod(r)) 76 } 77 78 func DecodeBytes(r *goja.Runtime, arg, enc goja.Value) []byte { 79 switch arg.ExportType() { 80 case reflectTypeArrayBuffer: 81 return arg.Export().(goja.ArrayBuffer).Bytes() 82 case reflectTypeString: 83 var codec StringCodec 84 if !goja.IsUndefined(enc) { 85 codec = stringCodecs[enc.String()] 86 } 87 if codec == nil { 88 codec = utf8Codec 89 } 90 return codec.DecodeAppend(arg.String(), nil) 91 default: 92 if o, ok := arg.(*goja.Object); ok { 93 if o.ExportType() == reflectTypeBytes { 94 return o.Export().([]byte) 95 } 96 } 97 } 98 panic(errors.NewTypeError(r, errors.ErrCodeInvalidArgType, "The \"data\" argument must be of type string or an instance of Buffer, TypedArray, or DataView.")) 99 } 100 101 func WrapBytes(r *goja.Runtime, data []byte) *goja.Object { 102 m := mod(r) 103 if api := api(m); api != nil { 104 return api.WrapBytes(data) 105 } 106 if from, ok := goja.AssertFunction(m.Get("from")); ok { 107 ab := r.NewArrayBuffer(data) 108 v, err := from(m, r.ToValue(ab)) 109 if err != nil { 110 panic(err) 111 } 112 return v.ToObject(r) 113 } 114 panic(r.NewTypeError("Buffer.from is not a function")) 115 } 116 117 // EncodeBytes returns the given byte slice encoded as string with the given encoding. If encoding 118 // is not specified or not supported, returns a Buffer that wraps the data. 119 func EncodeBytes(r *goja.Runtime, data []byte, enc goja.Value) goja.Value { 120 var codec StringCodec 121 if !goja.IsUndefined(enc) { 122 codec = StringCodecByName(enc.String()) 123 } 124 if codec != nil { 125 return r.ToValue(codec.Encode(data)) 126 } 127 return WrapBytes(r, data) 128 } 129 130 func (b *Buffer) WrapBytes(data []byte) *goja.Object { 131 return b.fromBytes(data) 132 } 133 134 func (b *Buffer) ctor(call goja.ConstructorCall) (res *goja.Object) { 135 arg := call.Argument(0) 136 switch arg.ExportType() { 137 case reflectTypeInt, reflectTypeFloat: 138 panic(b.r.NewTypeError("Calling the Buffer constructor with numeric argument is not implemented yet")) 139 // TODO implement 140 } 141 return b._from(call.Arguments...) 142 } 143 144 type StringCodec interface { 145 DecodeAppend(string, []byte) []byte 146 Encode([]byte) string 147 } 148 149 type hexCodec struct{} 150 151 func (hexCodec) DecodeAppend(s string, b []byte) []byte { 152 l := hex.DecodedLen(len(s)) 153 dst, res := expandSlice(b, l) 154 n, err := hex.Decode(dst, []byte(s)) 155 if err != nil { 156 res = res[:len(b)+n] 157 } 158 return res 159 } 160 161 func (hexCodec) Encode(b []byte) string { 162 return hex.EncodeToString(b) 163 } 164 165 type _utf8Codec struct{} 166 167 func (_utf8Codec) DecodeAppend(s string, b []byte) []byte { 168 r, _ := unicode.UTF8.NewEncoder().String(s) 169 dst, res := expandSlice(b, len(r)) 170 copy(dst, r) 171 return res 172 } 173 174 func (_utf8Codec) Encode(b []byte) string { 175 r, _ := unicode.UTF8.NewDecoder().Bytes(b) 176 return string(r) 177 } 178 179 type base64Codec struct{} 180 181 type base64UrlCodec struct { 182 base64Codec 183 } 184 185 func (base64Codec) DecodeAppend(s string, b []byte) []byte { 186 res, _ := Base64DecodeAppend(b, s) 187 return res 188 } 189 190 func (base64Codec) Encode(b []byte) string { 191 return base64.StdEncoding.EncodeToString(b) 192 } 193 194 func (base64UrlCodec) Encode(b []byte) string { 195 return base64.URLEncoding.EncodeToString(b) 196 } 197 198 var utf8Codec StringCodec = _utf8Codec{} 199 200 var stringCodecs = map[string]StringCodec{ 201 "hex": hexCodec{}, 202 "utf8": utf8Codec, 203 "utf-8": utf8Codec, 204 "base64": base64Codec{}, 205 "base64Url": base64UrlCodec{}, 206 } 207 208 func expandSlice(b []byte, l int) (dst, res []byte) { 209 if cap(b)-len(b) < l { 210 b1 := make([]byte, len(b)+l) 211 copy(b1, b) 212 dst = b1[len(b):] 213 res = b1 214 } else { 215 dst = b[len(b) : len(b)+l] 216 res = b[:len(b)+l] 217 } 218 return 219 } 220 221 func Base64DecodeAppend(dst []byte, src string) ([]byte, error) { 222 l := base64.StdEncoding.DecodedLen(len(src)) 223 d, res := expandSlice(dst, l) 224 srcBytes := []byte(src) 225 n, err := base64.StdEncoding.Decode(d, srcBytes) 226 if errPos, ok := err.(base64.CorruptInputError); ok { 227 if ch := src[errPos]; ch == '-' || ch == '_' { 228 start := int(errPos / 4 * 3) 229 n, err = base64.URLEncoding.Decode(d[start:], srcBytes[errPos&^3:]) 230 n += start 231 } 232 } 233 res = res[:len(dst)+n] 234 return res, err 235 } 236 237 func (b *Buffer) fromString(str, enc string) *goja.Object { 238 codec := stringCodecs[enc] 239 if codec == nil { 240 codec = utf8Codec 241 } 242 return b.fromBytes(codec.DecodeAppend(str, nil)) 243 } 244 245 func (b *Buffer) fromBytes(data []byte) *goja.Object { 246 o, err := b.uint8ArrayCtor(b.bufferCtorObj, b.r.ToValue(b.r.NewArrayBuffer(data))) 247 if err != nil { 248 panic(err) 249 } 250 return o 251 } 252 253 func (b *Buffer) _from(args ...goja.Value) *goja.Object { 254 if len(args) == 0 { 255 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")) 256 } 257 arg := args[0] 258 switch arg.ExportType() { 259 case reflectTypeArrayBuffer: 260 v, err := b.uint8ArrayCtor(b.bufferCtorObj, args...) 261 if err != nil { 262 panic(err) 263 } 264 return v 265 case reflectTypeString: 266 var enc string 267 if len(args) > 1 { 268 enc = args[1].String() 269 } 270 return b.fromString(arg.String(), enc) 271 default: 272 if o, ok := arg.(*goja.Object); ok { 273 if o.ExportType() == reflectTypeBytes { 274 bb, _ := o.Export().([]byte) 275 a := make([]byte, len(bb)) 276 copy(a, bb) 277 return b.fromBytes(a) 278 } else { 279 if f, ok := goja.AssertFunction(o.Get("valueOf")); ok { 280 valueOf, err := f(o) 281 if err != nil { 282 panic(err) 283 } 284 if valueOf != o { 285 args[0] = valueOf 286 return b._from(args...) 287 } 288 } 289 290 if s := o.GetSymbol(goja.SymToPrimitive); s != nil { 291 if f, ok := goja.AssertFunction(s); ok { 292 str, err := f(o, b.r.ToValue("string")) 293 if err != nil { 294 panic(err) 295 } 296 args[0] = str 297 return b._from(args...) 298 } 299 } 300 } 301 // array-like 302 if v := o.Get("length"); v != nil { 303 length := int(v.ToInteger()) 304 a := make([]byte, length) 305 for i := 0; i < length; i++ { 306 item := o.Get(strconv.Itoa(i)) 307 if item != nil { 308 a[i] = byte(item.ToInteger()) 309 } 310 } 311 return b.fromBytes(a) 312 } 313 } 314 } 315 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)) 316 } 317 318 func (b *Buffer) from(call goja.FunctionCall) goja.Value { 319 return b._from(call.Arguments...) 320 } 321 322 func isNumber(v goja.Value) bool { 323 switch v.ExportType() { 324 case reflectTypeInt, reflectTypeFloat: 325 return true 326 } 327 return false 328 } 329 330 func isString(v goja.Value) bool { 331 return v.ExportType() == reflectTypeString 332 } 333 334 func StringCodecByName(name string) StringCodec { 335 return stringCodecs[name] 336 } 337 338 func (b *Buffer) getStringCodec(enc goja.Value) (codec StringCodec) { 339 if !goja.IsUndefined(enc) { 340 codec = stringCodecs[enc.String()] 341 if codec == nil { 342 panic(errors.NewTypeError(b.r, "ERR_UNKNOWN_ENCODING", "Unknown encoding: %s", enc)) 343 } 344 } else { 345 codec = utf8Codec 346 } 347 return 348 } 349 350 func (b *Buffer) fill(buf []byte, fill string, enc goja.Value) []byte { 351 codec := b.getStringCodec(enc) 352 b1 := codec.DecodeAppend(fill, buf[:0]) 353 if len(b1) > len(buf) { 354 return b1[:len(buf)] 355 } 356 for i := len(b1); i < len(buf); { 357 i += copy(buf[i:], buf[:i]) 358 } 359 return buf 360 } 361 362 func (b *Buffer) alloc(call goja.FunctionCall) goja.Value { 363 arg0 := call.Argument(0) 364 size := -1 365 if isNumber(arg0) { 366 size = int(arg0.ToInteger()) 367 } 368 if size < 0 { 369 panic(errors.NewTypeError(b.r, errors.ErrCodeInvalidArgType, "The \"size\" argument must be of type number.")) 370 } 371 fill := call.Argument(1) 372 buf := make([]byte, size) 373 if !goja.IsUndefined(fill) { 374 if isString(fill) { 375 var enc goja.Value 376 if a := call.Argument(2); isString(a) { 377 enc = a 378 } else { 379 enc = goja.Undefined() 380 } 381 buf = b.fill(buf, fill.String(), enc) 382 } else { 383 fill = fill.ToNumber() 384 if !goja.IsNaN(fill) && !goja.IsInfinity(fill) { 385 fillByte := byte(fill.ToInteger()) 386 if fillByte != 0 { 387 for i := range buf { 388 buf[i] = fillByte 389 } 390 } 391 } 392 } 393 } 394 return b.fromBytes(buf) 395 } 396 397 func (b *Buffer) proto_toString(call goja.FunctionCall) goja.Value { 398 bb := Bytes(b.r, call.This) 399 codec := b.getStringCodec(call.Argument(0)) 400 return b.r.ToValue(codec.Encode(bb)) 401 } 402 403 func (b *Buffer) proto_equals(call goja.FunctionCall) goja.Value { 404 bb := Bytes(b.r, call.This) 405 other := call.Argument(0) 406 if b.r.InstanceOf(other, b.uint8ArrayCtorObj) { 407 otherBytes := Bytes(b.r, other) 408 return b.r.ToValue(bytes.Equal(bb, otherBytes)) 409 } 410 panic(errors.NewTypeError(b.r, errors.ErrCodeInvalidArgType, "The \"otherBuffer\" argument must be an instance of Buffer or Uint8Array.")) 411 } 412 413 func Require(runtime *goja.Runtime, module *goja.Object) { 414 b := &Buffer{r: runtime} 415 uint8Array := runtime.Get("Uint8Array") 416 if c, ok := goja.AssertConstructor(uint8Array); ok { 417 b.uint8ArrayCtor = c 418 } else { 419 panic(runtime.NewTypeError("Uint8Array is not a constructor")) 420 } 421 uint8ArrayObj := uint8Array.ToObject(runtime) 422 423 ctor := runtime.ToValue(b.ctor).ToObject(runtime) 424 ctor.SetPrototype(uint8ArrayObj) 425 ctor.DefineDataPropertySymbol(symApi, runtime.ToValue(b), goja.FLAG_FALSE, goja.FLAG_FALSE, goja.FLAG_FALSE) 426 b.bufferCtorObj = ctor 427 b.uint8ArrayCtorObj = uint8ArrayObj 428 429 proto := runtime.NewObject() 430 proto.SetPrototype(uint8ArrayObj.Get("prototype").ToObject(runtime)) 431 proto.DefineDataProperty("constructor", ctor, goja.FLAG_TRUE, goja.FLAG_TRUE, goja.FLAG_FALSE) 432 proto.Set("equals", b.proto_equals) 433 proto.Set("toString", b.proto_toString) 434 435 ctor.Set("prototype", proto) 436 ctor.Set("poolSize", 8192) 437 ctor.Set("from", b.from) 438 ctor.Set("alloc", b.alloc) 439 440 exports := module.Get("exports").(*goja.Object) 441 exports.Set("Buffer", ctor) 442 } 443 444 func init() { 445 require.RegisterCoreModule(ModuleName, Require) 446 }