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  }