builtin.go 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337
  1. package otto
  2. import (
  3. "encoding/hex"
  4. "errors"
  5. "math"
  6. "net/url"
  7. "regexp"
  8. "strconv"
  9. "strings"
  10. "unicode/utf16"
  11. "unicode/utf8"
  12. )
  13. // Global.
  14. func builtinGlobalEval(call FunctionCall) Value {
  15. src := call.Argument(0)
  16. if !src.IsString() {
  17. return src
  18. }
  19. rt := call.runtime
  20. program := rt.cmplParseOrThrow(src.string(), nil)
  21. if !call.eval {
  22. // Not a direct call to eval, so we enter the global ExecutionContext
  23. rt.enterGlobalScope()
  24. defer rt.leaveScope()
  25. }
  26. returnValue := rt.cmplEvaluateNodeProgram(program, true)
  27. if returnValue.isEmpty() {
  28. return Value{}
  29. }
  30. return returnValue
  31. }
  32. func builtinGlobalIsNaN(call FunctionCall) Value {
  33. value := call.Argument(0).float64()
  34. return boolValue(math.IsNaN(value))
  35. }
  36. func builtinGlobalIsFinite(call FunctionCall) Value {
  37. value := call.Argument(0).float64()
  38. return boolValue(!math.IsNaN(value) && !math.IsInf(value, 0))
  39. }
  40. func digitValue(chr rune) int {
  41. switch {
  42. case '0' <= chr && chr <= '9':
  43. return int(chr - '0')
  44. case 'a' <= chr && chr <= 'z':
  45. return int(chr - 'a' + 10)
  46. case 'A' <= chr && chr <= 'Z':
  47. return int(chr - 'A' + 10)
  48. }
  49. return 36 // Larger than any legal digit value
  50. }
  51. func builtinGlobalParseInt(call FunctionCall) Value {
  52. input := strings.Trim(call.Argument(0).string(), builtinStringTrimWhitespace)
  53. if len(input) == 0 {
  54. return NaNValue()
  55. }
  56. radix := int(toInt32(call.Argument(1)))
  57. negative := false
  58. switch input[0] {
  59. case '+':
  60. input = input[1:]
  61. case '-':
  62. negative = true
  63. input = input[1:]
  64. }
  65. strip := true
  66. if radix == 0 {
  67. radix = 10
  68. } else {
  69. if radix < 2 || radix > 36 {
  70. return NaNValue()
  71. } else if radix != 16 {
  72. strip = false
  73. }
  74. }
  75. switch len(input) {
  76. case 0:
  77. return NaNValue()
  78. case 1:
  79. default:
  80. if strip {
  81. if input[0] == '0' && (input[1] == 'x' || input[1] == 'X') {
  82. input = input[2:]
  83. radix = 16
  84. }
  85. }
  86. }
  87. base := radix
  88. index := 0
  89. for ; index < len(input); index++ {
  90. digit := digitValue(rune(input[index])) // If not ASCII, then an error anyway
  91. if digit >= base {
  92. break
  93. }
  94. }
  95. input = input[0:index]
  96. value, err := strconv.ParseInt(input, radix, 64)
  97. if err != nil {
  98. if errors.Is(err, strconv.ErrRange) {
  99. base := float64(base)
  100. // Could just be a very large number (e.g. 0x8000000000000000)
  101. var value float64
  102. for _, chr := range input {
  103. digit := float64(digitValue(chr))
  104. if digit >= base {
  105. return NaNValue()
  106. }
  107. value = value*base + digit
  108. }
  109. if negative {
  110. value *= -1
  111. }
  112. return float64Value(value)
  113. }
  114. return NaNValue()
  115. }
  116. if negative {
  117. value *= -1
  118. }
  119. return int64Value(value)
  120. }
  121. var (
  122. parseFloatMatchBadSpecial = regexp.MustCompile(`[\+\-]?(?:[Ii]nf$|infinity)`)
  123. parseFloatMatchValid = regexp.MustCompile(`[0-9eE\+\-\.]|Infinity`)
  124. )
  125. func builtinGlobalParseFloat(call FunctionCall) Value {
  126. // Caveat emptor: This implementation does NOT match the specification
  127. input := strings.Trim(call.Argument(0).string(), builtinStringTrimWhitespace)
  128. if parseFloatMatchBadSpecial.MatchString(input) {
  129. return NaNValue()
  130. }
  131. value, err := strconv.ParseFloat(input, 64)
  132. if err != nil {
  133. for end := len(input); end > 0; end-- {
  134. val := input[0:end]
  135. if !parseFloatMatchValid.MatchString(val) {
  136. return NaNValue()
  137. }
  138. value, err = strconv.ParseFloat(val, 64)
  139. if err == nil {
  140. break
  141. }
  142. }
  143. if err != nil {
  144. return NaNValue()
  145. }
  146. }
  147. return float64Value(value)
  148. }
  149. // encodeURI/decodeURI
  150. func encodeDecodeURI(call FunctionCall, escape *regexp.Regexp) Value {
  151. value := call.Argument(0)
  152. var input []uint16
  153. switch vl := value.value.(type) {
  154. case []uint16:
  155. input = vl
  156. default:
  157. input = utf16.Encode([]rune(value.string()))
  158. }
  159. if len(input) == 0 {
  160. return stringValue("")
  161. }
  162. output := []byte{}
  163. length := len(input)
  164. encode := make([]byte, 4)
  165. for index := 0; index < length; {
  166. value := input[index]
  167. decode := utf16.Decode(input[index : index+1])
  168. if value >= 0xDC00 && value <= 0xDFFF {
  169. panic(call.runtime.panicURIError("URI malformed"))
  170. }
  171. if value >= 0xD800 && value <= 0xDBFF {
  172. index++
  173. if index >= length {
  174. panic(call.runtime.panicURIError("URI malformed"))
  175. }
  176. // input = ..., value, value1, ...
  177. value1 := input[index]
  178. if value1 < 0xDC00 || value1 > 0xDFFF {
  179. panic(call.runtime.panicURIError("URI malformed"))
  180. }
  181. decode = []rune{((rune(value) - 0xD800) * 0x400) + (rune(value1) - 0xDC00) + 0x10000}
  182. }
  183. index++
  184. size := utf8.EncodeRune(encode, decode[0])
  185. output = append(output, encode[0:size]...)
  186. }
  187. bytes := escape.ReplaceAllFunc(output, func(target []byte) []byte {
  188. // Probably a better way of doing this
  189. if target[0] == ' ' {
  190. return []byte("%20")
  191. }
  192. return []byte(url.QueryEscape(string(target)))
  193. })
  194. return stringValue(string(bytes))
  195. }
  196. var encodeURIRegexp = regexp.MustCompile(`([^~!@#$&*()=:/,;?+'])`)
  197. func builtinGlobalEncodeURI(call FunctionCall) Value {
  198. return encodeDecodeURI(call, encodeURIRegexp)
  199. }
  200. var encodeURIComponentRegexp = regexp.MustCompile(`([^~!*()'])`)
  201. func builtinGlobalEncodeURIComponent(call FunctionCall) Value {
  202. return encodeDecodeURI(call, encodeURIComponentRegexp)
  203. }
  204. // 3B/2F/3F/3A/40/26/3D/2B/24/2C/23.
  205. var decodeURIGuard = regexp.MustCompile(`(?i)(?:%)(3B|2F|3F|3A|40|26|3D|2B|24|2C|23)`)
  206. func decodeURI(input string, reserve bool) (string, bool) {
  207. if reserve {
  208. input = decodeURIGuard.ReplaceAllString(input, "%25$1")
  209. }
  210. input = strings.ReplaceAll(input, "+", "%2B") // Ugly hack to make QueryUnescape work with our use case
  211. output, err := url.QueryUnescape(input)
  212. if err != nil || !utf8.ValidString(output) {
  213. return "", true
  214. }
  215. return output, false
  216. }
  217. func builtinGlobalDecodeURI(call FunctionCall) Value {
  218. output, err := decodeURI(call.Argument(0).string(), true)
  219. if err {
  220. panic(call.runtime.panicURIError("URI malformed"))
  221. }
  222. return stringValue(output)
  223. }
  224. func builtinGlobalDecodeURIComponent(call FunctionCall) Value {
  225. output, err := decodeURI(call.Argument(0).string(), false)
  226. if err {
  227. panic(call.runtime.panicURIError("URI malformed"))
  228. }
  229. return stringValue(output)
  230. }
  231. // escape/unescape
  232. func builtinShouldEscape(chr byte) bool {
  233. if 'A' <= chr && chr <= 'Z' || 'a' <= chr && chr <= 'z' || '0' <= chr && chr <= '9' {
  234. return false
  235. }
  236. return !strings.ContainsRune("*_+-./", rune(chr))
  237. }
  238. const escapeBase16 = "0123456789ABCDEF"
  239. func builtinEscape(input string) string {
  240. output := make([]byte, 0, len(input))
  241. length := len(input)
  242. for index := 0; index < length; {
  243. if builtinShouldEscape(input[index]) {
  244. chr, width := utf8.DecodeRuneInString(input[index:])
  245. chr16 := utf16.Encode([]rune{chr})[0]
  246. if 256 > chr16 {
  247. output = append(output, '%',
  248. escapeBase16[chr16>>4],
  249. escapeBase16[chr16&15],
  250. )
  251. } else {
  252. output = append(output, '%', 'u',
  253. escapeBase16[chr16>>12],
  254. escapeBase16[(chr16>>8)&15],
  255. escapeBase16[(chr16>>4)&15],
  256. escapeBase16[chr16&15],
  257. )
  258. }
  259. index += width
  260. } else {
  261. output = append(output, input[index])
  262. index++
  263. }
  264. }
  265. return string(output)
  266. }
  267. func builtinUnescape(input string) string {
  268. output := make([]rune, 0, len(input))
  269. length := len(input)
  270. for index := 0; index < length; {
  271. if input[index] == '%' {
  272. if index <= length-6 && input[index+1] == 'u' {
  273. byte16, err := hex.DecodeString(input[index+2 : index+6])
  274. if err == nil {
  275. value := uint16(byte16[0])<<8 + uint16(byte16[1])
  276. chr := utf16.Decode([]uint16{value})[0]
  277. output = append(output, chr)
  278. index += 6
  279. continue
  280. }
  281. }
  282. if index <= length-3 {
  283. byte8, err := hex.DecodeString(input[index+1 : index+3])
  284. if err == nil {
  285. value := uint16(byte8[0])
  286. chr := utf16.Decode([]uint16{value})[0]
  287. output = append(output, chr)
  288. index += 3
  289. continue
  290. }
  291. }
  292. }
  293. output = append(output, rune(input[index]))
  294. index++
  295. }
  296. return string(output)
  297. }
  298. func builtinGlobalEscape(call FunctionCall) Value {
  299. return stringValue(builtinEscape(call.Argument(0).string()))
  300. }
  301. func builtinGlobalUnescape(call FunctionCall) Value {
  302. return stringValue(builtinUnescape(call.Argument(0).string()))
  303. }