github.com/iosif02/goja_nodejs@v1.0.1/url/url.go (about) 1 package url 2 3 import ( 4 "math" 5 "net/url" 6 "reflect" 7 "strconv" 8 "strings" 9 10 "github.com/iosif02/goja" 11 "github.com/iosif02/goja_nodejs/errors" 12 13 "golang.org/x/net/idna" 14 ) 15 16 const ( 17 URLNotAbsolute = "URL is not absolute" 18 InvalidURL = "Invalid URL" 19 InvalidBaseURL = "Invalid base URL" 20 InvalidHostname = "Invalid hostname" 21 ) 22 23 var ( 24 reflectTypeURL = reflect.TypeOf((*nodeURL)(nil)) 25 reflectTypeInt = reflect.TypeOf(int64(0)) 26 ) 27 28 func toURL(r *goja.Runtime, v goja.Value) *nodeURL { 29 if v.ExportType() == reflectTypeURL { 30 if u := v.Export().(*nodeURL); u != nil { 31 return u 32 } 33 } 34 35 panic(errors.NewTypeError(r, errors.ErrCodeInvalidThis, `Value of "this" must be of type URL`)) 36 } 37 38 func (m *urlModule) newInvalidURLError(msg, input string) *goja.Object { 39 o := errors.NewTypeError(m.r, "ERR_INVALID_URL", msg) 40 o.Set("input", m.r.ToValue(input)) 41 return o 42 } 43 44 func (m *urlModule) defineURLAccessorProp(p *goja.Object, name string, getter func(*nodeURL) interface{}, setter func(*nodeURL, goja.Value)) { 45 var getterVal, setterVal goja.Value 46 if getter != nil { 47 getterVal = m.r.ToValue(func(call goja.FunctionCall) goja.Value { 48 return m.r.ToValue(getter(toURL(m.r, call.This))) 49 }) 50 } 51 if setter != nil { 52 setterVal = m.r.ToValue(func(call goja.FunctionCall) goja.Value { 53 setter(toURL(m.r, call.This), call.Argument(0)) 54 return goja.Undefined() 55 }) 56 } 57 p.DefineAccessorProperty(name, getterVal, setterVal, goja.FLAG_FALSE, goja.FLAG_TRUE) 58 } 59 60 func valueToURLPort(v goja.Value) (portNum int, empty bool) { 61 portNum = -1 62 if et := v.ExportType(); et == reflectTypeInt { 63 num := v.ToInteger() 64 if num < 0 { 65 empty = true 66 } else if num <= math.MaxUint16 { 67 portNum = int(num) 68 } 69 } else { 70 s := v.String() 71 if s == "" { 72 return 0, true 73 } 74 firstDigitIdx := -1 75 for i := 0; i < len(s); i++ { 76 if c := s[i]; c >= '0' && c <= '9' { 77 firstDigitIdx = i 78 break 79 } 80 } 81 82 if firstDigitIdx == -1 { 83 return -1, false 84 } 85 86 if firstDigitIdx > 0 { 87 return 0, true 88 } 89 90 for i := 0; i < len(s); i++ { 91 if c := s[i]; c >= '0' && c <= '9' { 92 if portNum == -1 { 93 portNum = 0 94 } 95 portNum = portNum*10 + int(c-'0') 96 if portNum > math.MaxUint16 { 97 portNum = -1 98 break 99 } 100 } else { 101 break 102 } 103 } 104 } 105 return 106 } 107 108 func isDefaultURLPort(protocol string, port int) bool { 109 switch port { 110 case 21: 111 if protocol == "ftp" { 112 return true 113 } 114 case 80: 115 if protocol == "http" || protocol == "ws" { 116 return true 117 } 118 case 443: 119 if protocol == "https" || protocol == "wss" { 120 return true 121 } 122 } 123 return false 124 } 125 126 func isSpecialProtocol(protocol string) bool { 127 switch protocol { 128 case "ftp", "file", "http", "https", "ws", "wss": 129 return true 130 } 131 return false 132 } 133 134 func clearURLPort(u *url.URL) { 135 u.Host = u.Hostname() 136 } 137 138 func setURLPort(nu *nodeURL, v goja.Value) { 139 u := nu.url 140 if u.Scheme == "file" { 141 return 142 } 143 portNum, empty := valueToURLPort(v) 144 if empty { 145 clearURLPort(u) 146 return 147 } 148 if portNum == -1 { 149 return 150 } 151 if isDefaultURLPort(u.Scheme, portNum) { 152 clearURLPort(u) 153 } else { 154 u.Host = u.Hostname() + ":" + strconv.Itoa(portNum) 155 } 156 } 157 158 func (m *urlModule) parseURL(s string, isBase bool) *url.URL { 159 u, err := url.Parse(s) 160 if err != nil { 161 if isBase { 162 panic(m.newInvalidURLError(InvalidBaseURL, s)) 163 } else { 164 panic(m.newInvalidURLError(InvalidURL, s)) 165 } 166 } 167 if isBase && !u.IsAbs() { 168 panic(m.newInvalidURLError(URLNotAbsolute, s)) 169 } 170 if portStr := u.Port(); portStr != "" { 171 if port, err := strconv.Atoi(portStr); err != nil || isDefaultURLPort(u.Scheme, port) { 172 u.Host = u.Hostname() // Clear port 173 } 174 } 175 m.fixURL(u) 176 return u 177 } 178 179 func fixRawQuery(u *url.URL) { 180 if u.RawQuery != "" { 181 u.RawQuery = escape(u.RawQuery, &tblEscapeURLQuery, false) 182 } 183 } 184 185 func (m *urlModule) fixURL(u *url.URL) { 186 switch u.Scheme { 187 case "https", "http", "ftp", "wss", "ws": 188 if u.Path == "" { 189 u.Path = "/" 190 } 191 hostname := u.Hostname() 192 lh := strings.ToLower(hostname) 193 ch, err := idna.Punycode.ToASCII(lh) 194 if err != nil { 195 panic(m.newInvalidURLError(InvalidHostname, lh)) 196 } 197 if ch != hostname { 198 if port := u.Port(); port != "" { 199 u.Host = ch + ":" + port 200 } else { 201 u.Host = ch 202 } 203 } 204 } 205 fixRawQuery(u) 206 } 207 208 func (m *urlModule) createURLPrototype() *goja.Object { 209 p := m.r.NewObject() 210 211 // host 212 m.defineURLAccessorProp(p, "host", func(u *nodeURL) interface{} { 213 return u.url.Host 214 }, func(u *nodeURL, arg goja.Value) { 215 host := arg.String() 216 if _, err := url.ParseRequestURI(u.url.Scheme + "://" + host); err == nil { 217 u.url.Host = host 218 m.fixURL(u.url) 219 } 220 }) 221 222 // hash 223 m.defineURLAccessorProp(p, "hash", func(u *nodeURL) interface{} { 224 if u.url.Fragment != "" { 225 return "#" + u.url.EscapedFragment() 226 } 227 return "" 228 }, func(u *nodeURL, arg goja.Value) { 229 h := arg.String() 230 if len(h) > 0 && h[0] == '#' { 231 h = h[1:] 232 } 233 u.url.Fragment = h 234 }) 235 236 // hostname 237 m.defineURLAccessorProp(p, "hostname", func(u *nodeURL) interface{} { 238 return strings.Split(u.url.Host, ":")[0] 239 }, func(u *nodeURL, arg goja.Value) { 240 h := arg.String() 241 if strings.IndexByte(h, ':') >= 0 { 242 return 243 } 244 if _, err := url.ParseRequestURI(u.url.Scheme + "://" + h); err == nil { 245 if port := u.url.Port(); port != "" { 246 u.url.Host = h + ":" + port 247 } else { 248 u.url.Host = h 249 } 250 m.fixURL(u.url) 251 } 252 }) 253 254 // href 255 m.defineURLAccessorProp(p, "href", func(u *nodeURL) interface{} { 256 return u.String() 257 }, func(u *nodeURL, arg goja.Value) { 258 u.url = m.parseURL(arg.String(), true) 259 }) 260 261 // pathname 262 m.defineURLAccessorProp(p, "pathname", func(u *nodeURL) interface{} { 263 return u.url.EscapedPath() 264 }, func(u *nodeURL, arg goja.Value) { 265 p := arg.String() 266 if _, err := url.Parse(p); err == nil { 267 switch u.url.Scheme { 268 case "https", "http", "ftp", "ws", "wss": 269 if !strings.HasPrefix(p, "/") { 270 p = "/" + p 271 } 272 } 273 u.url.Path = p 274 } 275 }) 276 277 // origin 278 m.defineURLAccessorProp(p, "origin", func(u *nodeURL) interface{} { 279 return u.url.Scheme + "://" + u.url.Hostname() 280 }, nil) 281 282 // password 283 m.defineURLAccessorProp(p, "password", func(u *nodeURL) interface{} { 284 p, _ := u.url.User.Password() 285 return p 286 }, func(u *nodeURL, arg goja.Value) { 287 user := u.url.User 288 u.url.User = url.UserPassword(user.Username(), arg.String()) 289 }) 290 291 // username 292 m.defineURLAccessorProp(p, "username", func(u *nodeURL) interface{} { 293 return u.url.User.Username() 294 }, func(u *nodeURL, arg goja.Value) { 295 p, has := u.url.User.Password() 296 if !has { 297 u.url.User = url.User(arg.String()) 298 } else { 299 u.url.User = url.UserPassword(arg.String(), p) 300 } 301 }) 302 303 // port 304 m.defineURLAccessorProp(p, "port", func(u *nodeURL) interface{} { 305 return u.url.Port() 306 }, func(u *nodeURL, arg goja.Value) { 307 setURLPort(u, arg) 308 }) 309 310 // protocol 311 m.defineURLAccessorProp(p, "protocol", func(u *nodeURL) interface{} { 312 return u.url.Scheme + ":" 313 }, func(u *nodeURL, arg goja.Value) { 314 s := arg.String() 315 pos := strings.IndexByte(s, ':') 316 if pos >= 0 { 317 s = s[:pos] 318 } 319 s = strings.ToLower(s) 320 if isSpecialProtocol(u.url.Scheme) == isSpecialProtocol(s) { 321 if _, err := url.ParseRequestURI(s + "://" + u.url.Host); err == nil { 322 u.url.Scheme = s 323 } 324 } 325 }) 326 327 // Search 328 m.defineURLAccessorProp(p, "search", func(u *nodeURL) interface{} { 329 u.syncSearchParams() 330 if u.url.RawQuery != "" { 331 return "?" + u.url.RawQuery 332 } 333 return "" 334 }, func(u *nodeURL, arg goja.Value) { 335 u.url.RawQuery = arg.String() 336 fixRawQuery(u.url) 337 if u.searchParams != nil { 338 u.searchParams = parseSearchQuery(u.url.RawQuery) 339 if u.searchParams == nil { 340 u.searchParams = make(searchParams, 0) 341 } 342 } 343 }) 344 345 // search Params 346 m.defineURLAccessorProp(p, "searchParams", func(u *nodeURL) interface{} { 347 sp := parseSearchQuery(u.url.RawQuery) 348 if sp == nil { 349 sp = make(searchParams, 0) 350 } 351 u.searchParams = sp 352 return m.newURLSearchParams((*urlSearchParams)(u)) 353 }, nil) 354 355 p.Set("toString", m.r.ToValue(func(call goja.FunctionCall) goja.Value { 356 u := toURL(m.r, call.This) 357 u.syncSearchParams() 358 return m.r.ToValue(u.url.String()) 359 })) 360 361 p.Set("toJSON", m.r.ToValue(func(call goja.FunctionCall) goja.Value { 362 u := toURL(m.r, call.This) 363 u.syncSearchParams() 364 return m.r.ToValue(u.url.String()) 365 })) 366 367 return p 368 } 369 370 func (m *urlModule) createURLConstructor() goja.Value { 371 f := m.r.ToValue(func(call goja.ConstructorCall) *goja.Object { 372 var u *url.URL 373 if baseArg := call.Argument(1); !goja.IsUndefined(baseArg) { 374 base := m.parseURL(baseArg.String(), true) 375 ref := m.parseURL(call.Argument(0).String(), false) 376 u = base.ResolveReference(ref) 377 } else { 378 u = m.parseURL(call.Argument(0).String(), true) 379 } 380 res := m.r.ToValue(&nodeURL{url: u}).(*goja.Object) 381 res.SetPrototype(call.This.Prototype()) 382 return res 383 }).(*goja.Object) 384 385 proto := m.createURLPrototype() 386 f.Set("prototype", proto) 387 proto.DefineDataProperty("constructor", f, goja.FLAG_FALSE, goja.FLAG_FALSE, goja.FLAG_FALSE) 388 return f 389 } 390 391 func (m *urlModule) domainToASCII(domUnicode string) string { 392 res, err := idna.ToASCII(domUnicode) 393 if err != nil { 394 return "" 395 } 396 return res 397 } 398 399 func (m *urlModule) domainToUnicode(domASCII string) string { 400 res, err := idna.ToUnicode(domASCII) 401 if err != nil { 402 return "" 403 } 404 return res 405 }