package goja import ( "errors" "io" "math" "regexp" "strconv" "strings" "sync" "unicode/utf8" "apigo.cc/ai/ai/goja/unistring" ) const hexUpper = "0123456789ABCDEF" var ( parseFloatRegexp = regexp.MustCompile(`^([+-]?(?:Infinity|[0-9]*\.?[0-9]*(?:[eE][+-]?[0-9]+)?))`) ) func (r *Runtime) builtin_isNaN(call FunctionCall) Value { if math.IsNaN(call.Argument(0).ToFloat()) { return valueTrue } else { return valueFalse } } func (r *Runtime) builtin_parseInt(call FunctionCall) Value { str := call.Argument(0).toString().toTrimmedUTF8() radix := int(toInt32(call.Argument(1))) v, _ := parseInt(str, radix) return v } func (r *Runtime) builtin_parseFloat(call FunctionCall) Value { m := parseFloatRegexp.FindStringSubmatch(call.Argument(0).toString().toTrimmedUTF8()) if len(m) == 2 { if s := m[1]; s != "" && s != "+" && s != "-" { switch s { case "+", "-": case "Infinity", "+Infinity": return _positiveInf case "-Infinity": return _negativeInf default: f, err := strconv.ParseFloat(s, 64) if err == nil || isRangeErr(err) { return floatToValue(f) } } } } return _NaN } func (r *Runtime) builtin_isFinite(call FunctionCall) Value { f := call.Argument(0).ToFloat() if math.IsNaN(f) || math.IsInf(f, 0) { return valueFalse } return valueTrue } func (r *Runtime) _encode(uriString String, unescaped *[256]bool) String { reader := uriString.Reader() utf8Buf := make([]byte, utf8.UTFMax) needed := false l := 0 for { rn, _, err := reader.ReadRune() if err != nil { if err != io.EOF { panic(r.newError(r.getURIError(), "Malformed URI")) } break } if rn >= utf8.RuneSelf { needed = true l += utf8.EncodeRune(utf8Buf, rn) * 3 } else if !unescaped[rn] { needed = true l += 3 } else { l++ } } if !needed { return uriString } buf := make([]byte, l) i := 0 reader = uriString.Reader() for { rn, _, err := reader.ReadRune() if err == io.EOF { break } if rn >= utf8.RuneSelf { n := utf8.EncodeRune(utf8Buf, rn) for _, b := range utf8Buf[:n] { buf[i] = '%' buf[i+1] = hexUpper[b>>4] buf[i+2] = hexUpper[b&15] i += 3 } } else if !unescaped[rn] { buf[i] = '%' buf[i+1] = hexUpper[rn>>4] buf[i+2] = hexUpper[rn&15] i += 3 } else { buf[i] = byte(rn) i++ } } return asciiString(buf) } func (r *Runtime) _decode(sv String, reservedSet *[256]bool) String { s := sv.String() hexCount := 0 for i := 0; i < len(s); { switch s[i] { case '%': if i+2 >= len(s) || !ishex(s[i+1]) || !ishex(s[i+2]) { panic(r.newError(r.getURIError(), "Malformed URI")) } c := unhex(s[i+1])<<4 | unhex(s[i+2]) if !reservedSet[c] { hexCount++ } i += 3 default: i++ } } if hexCount == 0 { return sv } t := make([]byte, len(s)-hexCount*2) j := 0 isUnicode := false for i := 0; i < len(s); { ch := s[i] switch ch { case '%': c := unhex(s[i+1])<<4 | unhex(s[i+2]) if reservedSet[c] { t[j] = s[i] t[j+1] = s[i+1] t[j+2] = s[i+2] j += 3 } else { t[j] = c if c >= utf8.RuneSelf { isUnicode = true } j++ } i += 3 default: if ch >= utf8.RuneSelf { isUnicode = true } t[j] = ch j++ i++ } } if !isUnicode { return asciiString(t) } us := make([]rune, 0, len(s)) for len(t) > 0 { rn, size := utf8.DecodeRune(t) if rn == utf8.RuneError { if size != 3 || t[0] != 0xef || t[1] != 0xbf || t[2] != 0xbd { panic(r.newError(r.getURIError(), "Malformed URI")) } } us = append(us, rn) t = t[size:] } return unicodeStringFromRunes(us) } func ishex(c byte) bool { switch { case '0' <= c && c <= '9': return true case 'a' <= c && c <= 'f': return true case 'A' <= c && c <= 'F': return true } return false } func unhex(c byte) byte { switch { case '0' <= c && c <= '9': return c - '0' case 'a' <= c && c <= 'f': return c - 'a' + 10 case 'A' <= c && c <= 'F': return c - 'A' + 10 } return 0 } func (r *Runtime) builtin_decodeURI(call FunctionCall) Value { uriString := call.Argument(0).toString() return r._decode(uriString, &uriReservedHash) } func (r *Runtime) builtin_decodeURIComponent(call FunctionCall) Value { uriString := call.Argument(0).toString() return r._decode(uriString, &emptyEscapeSet) } func (r *Runtime) builtin_encodeURI(call FunctionCall) Value { uriString := call.Argument(0).toString() return r._encode(uriString, &uriReservedUnescapedHash) } func (r *Runtime) builtin_encodeURIComponent(call FunctionCall) Value { uriString := call.Argument(0).toString() return r._encode(uriString, &uriUnescaped) } func (r *Runtime) builtin_escape(call FunctionCall) Value { s := call.Argument(0).toString() var sb strings.Builder l := s.Length() for i := 0; i < l; i++ { r := s.CharAt(i) if r >= 'A' && r <= 'Z' || r >= 'a' && r <= 'z' || r >= '0' && r <= '9' || r == '@' || r == '*' || r == '_' || r == '+' || r == '-' || r == '.' || r == '/' { sb.WriteByte(byte(r)) } else if r <= 0xff { sb.WriteByte('%') sb.WriteByte(hexUpper[r>>4]) sb.WriteByte(hexUpper[r&0xf]) } else { sb.WriteString("%u") sb.WriteByte(hexUpper[r>>12]) sb.WriteByte(hexUpper[(r>>8)&0xf]) sb.WriteByte(hexUpper[(r>>4)&0xf]) sb.WriteByte(hexUpper[r&0xf]) } } return asciiString(sb.String()) } func (r *Runtime) builtin_unescape(call FunctionCall) Value { s := call.Argument(0).toString() l := s.Length() var asciiBuf []byte var unicodeBuf []uint16 _, u := devirtualizeString(s) unicode := u != nil if unicode { unicodeBuf = make([]uint16, 1, l+1) unicodeBuf[0] = unistring.BOM } else { asciiBuf = make([]byte, 0, l) } for i := 0; i < l; { r := s.CharAt(i) if r == '%' { if i <= l-6 && s.CharAt(i+1) == 'u' { c0 := s.CharAt(i + 2) c1 := s.CharAt(i + 3) c2 := s.CharAt(i + 4) c3 := s.CharAt(i + 5) if c0 <= 0xff && ishex(byte(c0)) && c1 <= 0xff && ishex(byte(c1)) && c2 <= 0xff && ishex(byte(c2)) && c3 <= 0xff && ishex(byte(c3)) { r = uint16(unhex(byte(c0)))<<12 | uint16(unhex(byte(c1)))<<8 | uint16(unhex(byte(c2)))<<4 | uint16(unhex(byte(c3))) i += 5 goto out } } if i <= l-3 { c0 := s.CharAt(i + 1) c1 := s.CharAt(i + 2) if c0 <= 0xff && ishex(byte(c0)) && c1 <= 0xff && ishex(byte(c1)) { r = uint16(unhex(byte(c0))<<4 | unhex(byte(c1))) i += 2 } } } out: if r >= utf8.RuneSelf && !unicode { unicodeBuf = make([]uint16, 1, l+1) unicodeBuf[0] = unistring.BOM for _, b := range asciiBuf { unicodeBuf = append(unicodeBuf, uint16(b)) } asciiBuf = nil unicode = true } if unicode { unicodeBuf = append(unicodeBuf, r) } else { asciiBuf = append(asciiBuf, byte(r)) } i++ } if unicode { return unicodeString(unicodeBuf) } return asciiString(asciiBuf) } func createGlobalObjectTemplate() *objectTemplate { t := newObjectTemplate() t.protoFactory = func(r *Runtime) *Object { return r.global.ObjectPrototype } t.putStr("Object", func(r *Runtime) Value { return valueProp(r.getObject(), true, false, true) }) t.putStr("Function", func(r *Runtime) Value { return valueProp(r.getFunction(), true, false, true) }) t.putStr("Array", func(r *Runtime) Value { return valueProp(r.getArray(), true, false, true) }) t.putStr("String", func(r *Runtime) Value { return valueProp(r.getString(), true, false, true) }) t.putStr("Number", func(r *Runtime) Value { return valueProp(r.getNumber(), true, false, true) }) t.putStr("BigInt", func(r *Runtime) Value { return valueProp(r.getBigInt(), true, false, true) }) t.putStr("RegExp", func(r *Runtime) Value { return valueProp(r.getRegExp(), true, false, true) }) t.putStr("Date", func(r *Runtime) Value { return valueProp(r.getDate(), true, false, true) }) t.putStr("Boolean", func(r *Runtime) Value { return valueProp(r.getBoolean(), true, false, true) }) t.putStr("Proxy", func(r *Runtime) Value { return valueProp(r.getProxy(), true, false, true) }) t.putStr("Reflect", func(r *Runtime) Value { return valueProp(r.getReflect(), true, false, true) }) t.putStr("Error", func(r *Runtime) Value { return valueProp(r.getError(), true, false, true) }) t.putStr("AggregateError", func(r *Runtime) Value { return valueProp(r.getAggregateError(), true, false, true) }) t.putStr("TypeError", func(r *Runtime) Value { return valueProp(r.getTypeError(), true, false, true) }) t.putStr("ReferenceError", func(r *Runtime) Value { return valueProp(r.getReferenceError(), true, false, true) }) t.putStr("SyntaxError", func(r *Runtime) Value { return valueProp(r.getSyntaxError(), true, false, true) }) t.putStr("RangeError", func(r *Runtime) Value { return valueProp(r.getRangeError(), true, false, true) }) t.putStr("EvalError", func(r *Runtime) Value { return valueProp(r.getEvalError(), true, false, true) }) t.putStr("URIError", func(r *Runtime) Value { return valueProp(r.getURIError(), true, false, true) }) t.putStr("GoError", func(r *Runtime) Value { return valueProp(r.getGoError(), true, false, true) }) t.putStr("eval", func(r *Runtime) Value { return valueProp(r.getEval(), true, false, true) }) t.putStr("Math", func(r *Runtime) Value { return valueProp(r.getMath(), true, false, true) }) t.putStr("JSON", func(r *Runtime) Value { return valueProp(r.getJSON(), true, false, true) }) addTypedArrays(t) t.putStr("Symbol", func(r *Runtime) Value { return valueProp(r.getSymbol(), true, false, true) }) t.putStr("WeakSet", func(r *Runtime) Value { return valueProp(r.getWeakSet(), true, false, true) }) t.putStr("WeakMap", func(r *Runtime) Value { return valueProp(r.getWeakMap(), true, false, true) }) t.putStr("Map", func(r *Runtime) Value { return valueProp(r.getMap(), true, false, true) }) t.putStr("Set", func(r *Runtime) Value { return valueProp(r.getSet(), true, false, true) }) t.putStr("Promise", func(r *Runtime) Value { return valueProp(r.getPromise(), true, false, true) }) t.putStr("globalThis", func(r *Runtime) Value { return valueProp(r.globalObject, true, false, true) }) t.putStr("NaN", func(r *Runtime) Value { return valueProp(_NaN, false, false, false) }) t.putStr("undefined", func(r *Runtime) Value { return valueProp(_undefined, false, false, false) }) t.putStr("Infinity", func(r *Runtime) Value { return valueProp(_positiveInf, false, false, false) }) t.putStr("isNaN", func(r *Runtime) Value { return r.methodProp(r.builtin_isNaN, "isNaN", 1) }) t.putStr("parseInt", func(r *Runtime) Value { return valueProp(r.getParseInt(), true, false, true) }) t.putStr("parseFloat", func(r *Runtime) Value { return valueProp(r.getParseFloat(), true, false, true) }) t.putStr("isFinite", func(r *Runtime) Value { return r.methodProp(r.builtin_isFinite, "isFinite", 1) }) t.putStr("decodeURI", func(r *Runtime) Value { return r.methodProp(r.builtin_decodeURI, "decodeURI", 1) }) t.putStr("decodeURIComponent", func(r *Runtime) Value { return r.methodProp(r.builtin_decodeURIComponent, "decodeURIComponent", 1) }) t.putStr("encodeURI", func(r *Runtime) Value { return r.methodProp(r.builtin_encodeURI, "encodeURI", 1) }) t.putStr("encodeURIComponent", func(r *Runtime) Value { return r.methodProp(r.builtin_encodeURIComponent, "encodeURIComponent", 1) }) t.putStr("escape", func(r *Runtime) Value { return r.methodProp(r.builtin_escape, "escape", 1) }) t.putStr("unescape", func(r *Runtime) Value { return r.methodProp(r.builtin_unescape, "unescape", 1) }) // TODO: Annex B t.putSym(SymToStringTag, func(r *Runtime) Value { return valueProp(asciiString(classGlobal), false, false, true) }) return t } var globalObjectTemplate *objectTemplate var globalObjectTemplateOnce sync.Once func getGlobalObjectTemplate() *objectTemplate { globalObjectTemplateOnce.Do(func() { globalObjectTemplate = createGlobalObjectTemplate() }) return globalObjectTemplate } func (r *Runtime) getEval() *Object { ret := r.global.Eval if ret == nil { ret = r.newNativeFunc(r.builtin_eval, "eval", 1) r.global.Eval = ret } return ret } func digitVal(d byte) int { var v byte switch { case '0' <= d && d <= '9': v = d - '0' case 'a' <= d && d <= 'z': v = d - 'a' + 10 case 'A' <= d && d <= 'Z': v = d - 'A' + 10 default: return 36 } return int(v) } // ECMAScript compatible version of strconv.ParseInt func parseInt(s string, base int) (Value, error) { var n int64 var err error var cutoff, maxVal int64 var sign bool i := 0 if len(s) < 1 { err = strconv.ErrSyntax goto Error } switch s[0] { case '-': sign = true s = s[1:] case '+': s = s[1:] } if len(s) < 1 { err = strconv.ErrSyntax goto Error } // Look for hex prefix. if s[0] == '0' && len(s) > 1 && (s[1] == 'x' || s[1] == 'X') { if base == 0 || base == 16 { base = 16 s = s[2:] } } switch { case len(s) < 1: err = strconv.ErrSyntax goto Error case 2 <= base && base <= 36: // valid base; nothing to do case base == 0: // Look for hex prefix. switch { case s[0] == '0' && len(s) > 1 && (s[1] == 'x' || s[1] == 'X'): if len(s) < 3 { err = strconv.ErrSyntax goto Error } base = 16 s = s[2:] default: base = 10 } default: err = errors.New("invalid base " + strconv.Itoa(base)) goto Error } // Cutoff is the smallest number such that cutoff*base > maxInt64. // Use compile-time constants for common cases. switch base { case 10: cutoff = math.MaxInt64/10 + 1 case 16: cutoff = math.MaxInt64/16 + 1 default: cutoff = math.MaxInt64/int64(base) + 1 } maxVal = math.MaxInt64 for ; i < len(s); i++ { if n >= cutoff { // n*base overflows return parseLargeInt(float64(n), s[i:], base, sign) } v := digitVal(s[i]) if v >= base { break } n *= int64(base) n1 := n + int64(v) if n1 < n || n1 > maxVal { // n+v overflows return parseLargeInt(float64(n)+float64(v), s[i+1:], base, sign) } n = n1 } if i == 0 { err = strconv.ErrSyntax goto Error } if sign { n = -n } return intToValue(n), nil Error: return _NaN, err } func parseLargeInt(n float64, s string, base int, sign bool) (Value, error) { i := 0 b := float64(base) for ; i < len(s); i++ { v := digitVal(s[i]) if v >= base { break } n = n*b + float64(v) } if sign { n = -n } // We know it can't be represented as int, so use valueFloat instead of floatToValue return valueFloat(n), nil } var ( uriUnescaped [256]bool uriReserved [256]bool uriReservedHash [256]bool uriReservedUnescapedHash [256]bool emptyEscapeSet [256]bool ) func init() { for _, c := range "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.!~*'()" { uriUnescaped[c] = true } for _, c := range ";/?:@&=+$," { uriReserved[c] = true } for i := 0; i < 256; i++ { if uriUnescaped[i] || uriReserved[i] { uriReservedUnescapedHash[i] = true } uriReservedHash[i] = uriReserved[i] } uriReservedUnescapedHash['#'] = true uriReservedHash['#'] = true }