Source file
src/net/url/url.go
1
2
3
4
5
6
7
8
9
10
11
12 package url
13
14
15
16
17 import (
18 "errors"
19 "fmt"
20 "internal/godebug"
21 "net/netip"
22 "path"
23 "slices"
24 "strconv"
25 "strings"
26 _ "unsafe"
27 )
28
29 var urlstrictcolons = godebug.New("urlstrictcolons")
30
31
32 type Error struct {
33 Op string
34 URL string
35 Err error
36 }
37
38 func (e *Error) Unwrap() error { return e.Err }
39 func (e *Error) Error() string { return fmt.Sprintf("%s %q: %s", e.Op, e.URL, e.Err) }
40
41 func (e *Error) Timeout() bool {
42 t, ok := e.Err.(interface {
43 Timeout() bool
44 })
45 return ok && t.Timeout()
46 }
47
48 func (e *Error) Temporary() bool {
49 t, ok := e.Err.(interface {
50 Temporary() bool
51 })
52 return ok && t.Temporary()
53 }
54
55 const upperhex = "0123456789ABCDEF"
56
57 func ishex(c byte) bool {
58 return table[c]&hexChar != 0
59 }
60
61
62 func unhex(c byte) byte {
63 return 9*(c>>6) + (c & 15)
64 }
65
66 type EscapeError string
67
68 func (e EscapeError) Error() string {
69 return "invalid URL escape " + strconv.Quote(string(e))
70 }
71
72 type InvalidHostError string
73
74 func (e InvalidHostError) Error() string {
75 return "invalid character " + strconv.Quote(string(e)) + " in host name"
76 }
77
78
79 func shouldEscape(c byte, mode encoding) bool {
80 return table[c]&mode == 0
81 }
82
83
84
85
86
87
88 func QueryUnescape(s string) (string, error) {
89 return unescape(s, encodeQueryComponent)
90 }
91
92
93
94
95
96
97
98
99 func PathUnescape(s string) (string, error) {
100 return unescape(s, encodePathSegment)
101 }
102
103
104
105 func unescape(s string, mode encoding) (string, error) {
106
107 n := 0
108 hasPlus := false
109 for i := 0; i < len(s); {
110 switch s[i] {
111 case '%':
112 n++
113 if i+2 >= len(s) || !ishex(s[i+1]) || !ishex(s[i+2]) {
114 s = s[i:]
115 if len(s) > 3 {
116 s = s[:3]
117 }
118 return "", EscapeError(s)
119 }
120
121
122
123
124
125
126 if mode == encodeHost && unhex(s[i+1]) < 8 && s[i:i+3] != "%25" {
127 return "", EscapeError(s[i : i+3])
128 }
129 if mode == encodeZone {
130
131
132
133
134
135
136
137 v := unhex(s[i+1])<<4 | unhex(s[i+2])
138 if s[i:i+3] != "%25" && v != ' ' && shouldEscape(v, encodeHost) {
139 return "", EscapeError(s[i : i+3])
140 }
141 }
142 i += 3
143 case '+':
144 hasPlus = mode == encodeQueryComponent
145 i++
146 default:
147 if (mode == encodeHost || mode == encodeZone) && s[i] < 0x80 && shouldEscape(s[i], mode) {
148 return "", InvalidHostError(s[i : i+1])
149 }
150 i++
151 }
152 }
153
154 if n == 0 && !hasPlus {
155 return s, nil
156 }
157
158 var unescapedPlusSign byte
159 switch mode {
160 case encodeQueryComponent:
161 unescapedPlusSign = ' '
162 default:
163 unescapedPlusSign = '+'
164 }
165 var t strings.Builder
166 t.Grow(len(s) - 2*n)
167 for i := 0; i < len(s); i++ {
168 switch s[i] {
169 case '%':
170
171
172 t.WriteByte(unhex(s[i+1])<<4 | unhex(s[i+2]))
173 i += 2
174 case '+':
175 t.WriteByte(unescapedPlusSign)
176 default:
177 t.WriteByte(s[i])
178 }
179 }
180 return t.String(), nil
181 }
182
183
184
185 func QueryEscape(s string) string {
186 return escape(s, encodeQueryComponent)
187 }
188
189
190
191 func PathEscape(s string) string {
192 return escape(s, encodePathSegment)
193 }
194
195 func escape(s string, mode encoding) string {
196 spaceCount, hexCount := 0, 0
197 for _, c := range []byte(s) {
198 if shouldEscape(c, mode) {
199 if c == ' ' && mode == encodeQueryComponent {
200 spaceCount++
201 } else {
202 hexCount++
203 }
204 }
205 }
206
207 if spaceCount == 0 && hexCount == 0 {
208 return s
209 }
210
211 var buf [64]byte
212 var t []byte
213
214 required := len(s) + 2*hexCount
215 if required <= len(buf) {
216 t = buf[:required]
217 } else {
218 t = make([]byte, required)
219 }
220
221 if hexCount == 0 {
222 copy(t, s)
223 for i := 0; i < len(s); i++ {
224 if s[i] == ' ' {
225 t[i] = '+'
226 }
227 }
228 return string(t)
229 }
230
231 j := 0
232 for _, c := range []byte(s) {
233 switch {
234 case c == ' ' && mode == encodeQueryComponent:
235 t[j] = '+'
236 j++
237 case shouldEscape(c, mode):
238 t[j] = '%'
239 t[j+1] = upperhex[c>>4]
240 t[j+2] = upperhex[c&15]
241 j += 3
242 default:
243 t[j] = c
244 j++
245 }
246 }
247 return string(t)
248 }
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275 type URL struct {
276 Scheme string
277 Opaque string
278 User *Userinfo
279 Host string
280 Path string
281 Fragment string
282
283
284
285 RawQuery string
286
287
288
289
290
291 RawPath string
292
293
294
295
296
297 RawFragment string
298
299
300
301 ForceQuery bool
302
303
304
305 OmitHost bool
306 }
307
308
309
310 func User(username string) *Userinfo {
311 return &Userinfo{username, "", false}
312 }
313
314
315
316
317
318
319
320
321
322 func UserPassword(username, password string) *Userinfo {
323 return &Userinfo{username, password, true}
324 }
325
326
327
328
329
330 type Userinfo struct {
331 username string
332 password string
333 passwordSet bool
334 }
335
336
337 func (u *Userinfo) Username() string {
338 if u == nil {
339 return ""
340 }
341 return u.username
342 }
343
344
345 func (u *Userinfo) Password() (string, bool) {
346 if u == nil {
347 return "", false
348 }
349 return u.password, u.passwordSet
350 }
351
352
353
354 func (u *Userinfo) String() string {
355 if u == nil {
356 return ""
357 }
358 s := escape(u.username, encodeUserPassword)
359 if u.passwordSet {
360 s += ":" + escape(u.password, encodeUserPassword)
361 }
362 return s
363 }
364
365
366
367
368 func getScheme(rawURL string) (scheme, path string, err error) {
369 for i := 0; i < len(rawURL); i++ {
370 c := rawURL[i]
371 switch {
372 case 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z':
373
374 case '0' <= c && c <= '9' || c == '+' || c == '-' || c == '.':
375 if i == 0 {
376 return "", rawURL, nil
377 }
378 case c == ':':
379 if i == 0 {
380 return "", "", errors.New("missing protocol scheme")
381 }
382 return rawURL[:i], rawURL[i+1:], nil
383 default:
384
385
386 return "", rawURL, nil
387 }
388 }
389 return "", rawURL, nil
390 }
391
392
393
394
395
396
397
398 func Parse(rawURL string) (*URL, error) {
399
400 u, frag, _ := strings.Cut(rawURL, "#")
401 url, err := parse(u, false)
402 if err != nil {
403 return nil, &Error{"parse", u, err}
404 }
405 if frag == "" {
406 return url, nil
407 }
408 if err = url.setFragment(frag); err != nil {
409 return nil, &Error{"parse", rawURL, err}
410 }
411 return url, nil
412 }
413
414
415
416
417
418
419 func ParseRequestURI(rawURL string) (*URL, error) {
420 url, err := parse(rawURL, true)
421 if err != nil {
422 return nil, &Error{"parse", rawURL, err}
423 }
424 return url, nil
425 }
426
427
428
429
430
431 func parse(rawURL string, viaRequest bool) (*URL, error) {
432 var rest string
433 var err error
434
435 if stringContainsCTLByte(rawURL) {
436 return nil, errors.New("net/url: invalid control character in URL")
437 }
438
439 if rawURL == "" && viaRequest {
440 return nil, errors.New("empty url")
441 }
442 url := new(URL)
443
444 if rawURL == "*" {
445 url.Path = "*"
446 return url, nil
447 }
448
449
450
451 if url.Scheme, rest, err = getScheme(rawURL); err != nil {
452 return nil, err
453 }
454 url.Scheme = strings.ToLower(url.Scheme)
455
456 if strings.HasSuffix(rest, "?") && strings.Count(rest, "?") == 1 {
457 url.ForceQuery = true
458 rest = rest[:len(rest)-1]
459 } else {
460 rest, url.RawQuery, _ = strings.Cut(rest, "?")
461 }
462
463 if !strings.HasPrefix(rest, "/") {
464 if url.Scheme != "" {
465
466 url.Opaque = rest
467 return url, nil
468 }
469 if viaRequest {
470 return nil, errors.New("invalid URI for request")
471 }
472
473
474
475
476
477
478
479 if segment, _, _ := strings.Cut(rest, "/"); strings.Contains(segment, ":") {
480
481 return nil, errors.New("first path segment in URL cannot contain colon")
482 }
483 }
484
485 if (url.Scheme != "" || !viaRequest && !strings.HasPrefix(rest, "///")) && strings.HasPrefix(rest, "//") {
486 var authority string
487 authority, rest = rest[2:], ""
488 if i := strings.Index(authority, "/"); i >= 0 {
489 authority, rest = authority[:i], authority[i:]
490 }
491 url.User, url.Host, err = parseAuthority(url.Scheme, authority)
492 if err != nil {
493 return nil, err
494 }
495 } else if url.Scheme != "" && strings.HasPrefix(rest, "/") {
496
497
498 url.OmitHost = true
499 }
500
501
502
503
504
505 if err := url.setPath(rest); err != nil {
506 return nil, err
507 }
508 return url, nil
509 }
510
511 func parseAuthority(scheme, authority string) (user *Userinfo, host string, err error) {
512 i := strings.LastIndex(authority, "@")
513 if i < 0 {
514 host, err = parseHost(scheme, authority)
515 } else {
516 host, err = parseHost(scheme, authority[i+1:])
517 }
518 if err != nil {
519 return nil, "", err
520 }
521 if i < 0 {
522 return nil, host, nil
523 }
524 userinfo := authority[:i]
525 if !validUserinfo(userinfo) {
526 return nil, "", errors.New("net/url: invalid userinfo")
527 }
528 if !strings.Contains(userinfo, ":") {
529 if userinfo, err = unescape(userinfo, encodeUserPassword); err != nil {
530 return nil, "", err
531 }
532 user = User(userinfo)
533 } else {
534 username, password, _ := strings.Cut(userinfo, ":")
535 if username, err = unescape(username, encodeUserPassword); err != nil {
536 return nil, "", err
537 }
538 if password, err = unescape(password, encodeUserPassword); err != nil {
539 return nil, "", err
540 }
541 user = UserPassword(username, password)
542 }
543 return user, host, nil
544 }
545
546
547
548 func parseHost(scheme, host string) (string, error) {
549 if openBracketIdx := strings.LastIndex(host, "["); openBracketIdx > 0 {
550 return "", errors.New("invalid IP-literal")
551 } else if openBracketIdx == 0 {
552
553
554 closeBracketIdx := strings.LastIndex(host, "]")
555 if closeBracketIdx < 0 {
556 return "", errors.New("missing ']' in host")
557 }
558
559 colonPort := host[closeBracketIdx+1:]
560 if !validOptionalPort(colonPort) {
561 return "", fmt.Errorf("invalid port %q after host", colonPort)
562 }
563 unescapedColonPort, err := unescape(colonPort, encodeHost)
564 if err != nil {
565 return "", err
566 }
567
568 hostname := host[openBracketIdx+1 : closeBracketIdx]
569 var unescapedHostname string
570
571
572
573
574
575
576 zoneIdx := strings.Index(hostname, "%25")
577 if zoneIdx >= 0 {
578 hostPart, err := unescape(hostname[:zoneIdx], encodeHost)
579 if err != nil {
580 return "", err
581 }
582 zonePart, err := unescape(hostname[zoneIdx:], encodeZone)
583 if err != nil {
584 return "", err
585 }
586 unescapedHostname = hostPart + zonePart
587 } else {
588 var err error
589 unescapedHostname, err = unescape(hostname, encodeHost)
590 if err != nil {
591 return "", err
592 }
593 }
594
595
596
597
598 addr, err := netip.ParseAddr(unescapedHostname)
599 if err != nil {
600 return "", fmt.Errorf("invalid host: %w", err)
601 }
602 if addr.Is4() {
603 return "", errors.New("invalid IP-literal")
604 }
605 return "[" + unescapedHostname + "]" + unescapedColonPort, nil
606 } else if i := strings.Index(host, ":"); i != -1 {
607 lastColon := strings.LastIndex(host, ":")
608 if lastColon != i {
609
610
611
612
613
614
615
616
617
618
619 if scheme == "http" || scheme == "https" {
620 if urlstrictcolons.Value() == "0" {
621 urlstrictcolons.IncNonDefault()
622 i = lastColon
623 }
624 } else {
625 i = lastColon
626 }
627 }
628 colonPort := host[i:]
629 if !validOptionalPort(colonPort) {
630 return "", fmt.Errorf("invalid port %q after host", colonPort)
631 }
632 }
633
634 var err error
635 if host, err = unescape(host, encodeHost); err != nil {
636 return "", err
637 }
638 return host, nil
639 }
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659 func (u *URL) setPath(p string) error {
660 path, err := unescape(p, encodePath)
661 if err != nil {
662 return err
663 }
664 u.Path = path
665 if escp := escape(path, encodePath); p == escp {
666
667 u.RawPath = ""
668 } else {
669 u.RawPath = p
670 }
671 return nil
672 }
673
674
675 func badSetPath(*URL, string) error
676
677
678
679
680
681
682
683
684
685
686 func (u *URL) EscapedPath() string {
687 if u.RawPath != "" && validEncoded(u.RawPath, encodePath) {
688 p, err := unescape(u.RawPath, encodePath)
689 if err == nil && p == u.Path {
690 return u.RawPath
691 }
692 }
693 if u.Path == "*" {
694 return "*"
695 }
696 return escape(u.Path, encodePath)
697 }
698
699
700
701
702 func validEncoded(s string, mode encoding) bool {
703 for i := 0; i < len(s); i++ {
704
705
706
707
708
709 switch s[i] {
710 case '!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=', ':', '@':
711
712 case '[', ']':
713
714 case '%':
715
716 default:
717 if shouldEscape(s[i], mode) {
718 return false
719 }
720 }
721 }
722 return true
723 }
724
725
726 func (u *URL) setFragment(f string) error {
727 frag, err := unescape(f, encodeFragment)
728 if err != nil {
729 return err
730 }
731 u.Fragment = frag
732 if escf := escape(frag, encodeFragment); f == escf {
733
734 u.RawFragment = ""
735 } else {
736 u.RawFragment = f
737 }
738 return nil
739 }
740
741
742
743
744
745
746
747
748
749 func (u *URL) EscapedFragment() string {
750 if u.RawFragment != "" && validEncoded(u.RawFragment, encodeFragment) {
751 f, err := unescape(u.RawFragment, encodeFragment)
752 if err == nil && f == u.Fragment {
753 return u.RawFragment
754 }
755 }
756 return escape(u.Fragment, encodeFragment)
757 }
758
759
760
761 func validOptionalPort(port string) bool {
762 if port == "" {
763 return true
764 }
765 if port[0] != ':' {
766 return false
767 }
768 for _, b := range port[1:] {
769 if b < '0' || b > '9' {
770 return false
771 }
772 }
773 return true
774 }
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797 func (u *URL) String() string {
798 var buf strings.Builder
799
800 n := len(u.Scheme)
801 if u.Opaque != "" {
802 n += len(u.Opaque)
803 } else {
804 if !u.OmitHost && (u.Scheme != "" || u.Host != "" || u.User != nil) {
805 username := u.User.Username()
806 password, _ := u.User.Password()
807 n += len(username) + len(password) + len(u.Host)
808 }
809 n += len(u.Path)
810 }
811 n += len(u.RawQuery) + len(u.RawFragment)
812 n += len(":" + "//" + "//" + ":" + "@" + "/" + "./" + "?" + "#")
813 buf.Grow(n)
814
815 if u.Scheme != "" {
816 buf.WriteString(u.Scheme)
817 buf.WriteByte(':')
818 }
819 if u.Opaque != "" {
820 buf.WriteString(u.Opaque)
821 } else {
822 if u.Scheme != "" || u.Host != "" || u.User != nil {
823 if u.OmitHost && u.Host == "" && u.User == nil {
824
825 } else {
826 if u.Host != "" || u.Path != "" || u.User != nil {
827 buf.WriteString("//")
828 }
829 if ui := u.User; ui != nil {
830 buf.WriteString(ui.String())
831 buf.WriteByte('@')
832 }
833 if h := u.Host; h != "" {
834 buf.WriteString(escape(h, encodeHost))
835 }
836 }
837 }
838 path := u.EscapedPath()
839 if path != "" && path[0] != '/' && u.Host != "" {
840 buf.WriteByte('/')
841 }
842 if buf.Len() == 0 {
843
844
845
846
847
848
849 if segment, _, _ := strings.Cut(path, "/"); strings.Contains(segment, ":") {
850 buf.WriteString("./")
851 }
852 }
853 buf.WriteString(path)
854 }
855 if u.ForceQuery || u.RawQuery != "" {
856 buf.WriteByte('?')
857 buf.WriteString(u.RawQuery)
858 }
859 if u.Fragment != "" {
860 buf.WriteByte('#')
861 buf.WriteString(u.EscapedFragment())
862 }
863 return buf.String()
864 }
865
866
867
868 func (u *URL) Redacted() string {
869 if u == nil {
870 return ""
871 }
872
873 ru := *u
874 if _, has := ru.User.Password(); has {
875 ru.User = UserPassword(ru.User.Username(), "xxxxx")
876 }
877 return ru.String()
878 }
879
880
881
882
883
884 type Values map[string][]string
885
886
887
888
889
890 func (v Values) Get(key string) string {
891 vs := v[key]
892 if len(vs) == 0 {
893 return ""
894 }
895 return vs[0]
896 }
897
898
899
900 func (v Values) Set(key, value string) {
901 v[key] = []string{value}
902 }
903
904
905
906 func (v Values) Add(key, value string) {
907 v[key] = append(v[key], value)
908 }
909
910
911 func (v Values) Del(key string) {
912 delete(v, key)
913 }
914
915
916 func (v Values) Has(key string) bool {
917 _, ok := v[key]
918 return ok
919 }
920
921
922
923
924
925
926
927
928
929
930
931 func ParseQuery(query string) (Values, error) {
932 m := make(Values)
933 err := parseQuery(m, query)
934 return m, err
935 }
936
937 var urlmaxqueryparams = godebug.New("urlmaxqueryparams")
938
939
940 const defaultMaxParams = 10000
941
942 func urlParamsWithinMax(params int) bool {
943 withinDefaultMax := params <= defaultMaxParams
944 if urlmaxqueryparams.Value() == "" {
945 return withinDefaultMax
946 }
947 customMax, err := strconv.Atoi(urlmaxqueryparams.Value())
948 if err != nil {
949 return withinDefaultMax
950 }
951 withinCustomMax := customMax == 0 || params < customMax
952 if withinDefaultMax != withinCustomMax {
953 urlmaxqueryparams.IncNonDefault()
954 }
955 return withinCustomMax
956 }
957
958 func parseQuery(m Values, query string) (err error) {
959 if !urlParamsWithinMax(strings.Count(query, "&") + 1) {
960 return errors.New("number of URL query parameters exceeded limit")
961 }
962 for query != "" {
963 var key string
964 key, query, _ = strings.Cut(query, "&")
965 if strings.Contains(key, ";") {
966 err = fmt.Errorf("invalid semicolon separator in query")
967 continue
968 }
969 if key == "" {
970 continue
971 }
972 key, value, _ := strings.Cut(key, "=")
973 key, err1 := QueryUnescape(key)
974 if err1 != nil {
975 if err == nil {
976 err = err1
977 }
978 continue
979 }
980 value, err1 = QueryUnescape(value)
981 if err1 != nil {
982 if err == nil {
983 err = err1
984 }
985 continue
986 }
987 m[key] = append(m[key], value)
988 }
989 return err
990 }
991
992
993
994 func (v Values) Encode() string {
995 if len(v) == 0 {
996 return ""
997 }
998 var buf strings.Builder
999
1000
1001 keys := make([]string, len(v))
1002 var i int
1003 for k := range v {
1004 keys[i] = k
1005 i++
1006 }
1007 slices.Sort(keys)
1008 for _, k := range keys {
1009 vs := v[k]
1010 keyEscaped := QueryEscape(k)
1011 for _, v := range vs {
1012 if buf.Len() > 0 {
1013 buf.WriteByte('&')
1014 }
1015 buf.WriteString(keyEscaped)
1016 buf.WriteByte('=')
1017 buf.WriteString(QueryEscape(v))
1018 }
1019 }
1020 return buf.String()
1021 }
1022
1023
1024
1025 func resolvePath(base, ref string) string {
1026 var full string
1027 if ref == "" {
1028 full = base
1029 } else if ref[0] != '/' {
1030 i := strings.LastIndex(base, "/")
1031 full = base[:i+1] + ref
1032 } else {
1033 full = ref
1034 }
1035 if full == "" {
1036 return ""
1037 }
1038
1039 var (
1040 elem string
1041 dst strings.Builder
1042 )
1043 first := true
1044 remaining := full
1045
1046 dst.WriteByte('/')
1047 found := true
1048 for found {
1049 elem, remaining, found = strings.Cut(remaining, "/")
1050 if elem == "." {
1051 first = false
1052
1053 continue
1054 }
1055
1056 if elem == ".." {
1057
1058 str := dst.String()[1:]
1059 index := strings.LastIndexByte(str, '/')
1060
1061 dst.Reset()
1062 dst.WriteByte('/')
1063 if index == -1 {
1064 first = true
1065 } else {
1066 dst.WriteString(str[:index])
1067 }
1068 } else {
1069 if !first {
1070 dst.WriteByte('/')
1071 }
1072 dst.WriteString(elem)
1073 first = false
1074 }
1075 }
1076
1077 if elem == "." || elem == ".." {
1078 dst.WriteByte('/')
1079 }
1080
1081
1082 r := dst.String()
1083 if len(r) > 1 && r[1] == '/' {
1084 r = r[1:]
1085 }
1086 return r
1087 }
1088
1089
1090
1091 func (u *URL) IsAbs() bool {
1092 return u.Scheme != ""
1093 }
1094
1095
1096
1097
1098 func (u *URL) Parse(ref string) (*URL, error) {
1099 refURL, err := Parse(ref)
1100 if err != nil {
1101 return nil, err
1102 }
1103 return u.ResolveReference(refURL), nil
1104 }
1105
1106
1107
1108
1109
1110
1111
1112 func (u *URL) ResolveReference(ref *URL) *URL {
1113 url := *ref
1114 if ref.Scheme == "" {
1115 url.Scheme = u.Scheme
1116 }
1117 if ref.Scheme != "" || ref.Host != "" || ref.User != nil {
1118
1119
1120
1121 url.setPath(resolvePath(ref.EscapedPath(), ""))
1122 return &url
1123 }
1124 if ref.Opaque != "" {
1125 url.User = nil
1126 url.Host = ""
1127 url.Path = ""
1128 return &url
1129 }
1130 if ref.Path == "" && !ref.ForceQuery && ref.RawQuery == "" {
1131 url.RawQuery = u.RawQuery
1132 if ref.Fragment == "" {
1133 url.Fragment = u.Fragment
1134 url.RawFragment = u.RawFragment
1135 }
1136 }
1137 if ref.Path == "" && u.Opaque != "" {
1138 url.Opaque = u.Opaque
1139 url.User = nil
1140 url.Host = ""
1141 url.Path = ""
1142 return &url
1143 }
1144
1145 url.Host = u.Host
1146 url.User = u.User
1147 url.setPath(resolvePath(u.EscapedPath(), ref.EscapedPath()))
1148 return &url
1149 }
1150
1151
1152
1153
1154 func (u *URL) Query() Values {
1155 v, _ := ParseQuery(u.RawQuery)
1156 return v
1157 }
1158
1159
1160
1161 func (u *URL) RequestURI() string {
1162 result := u.Opaque
1163 if result == "" {
1164 result = u.EscapedPath()
1165 if result == "" {
1166 result = "/"
1167 }
1168 } else {
1169 if strings.HasPrefix(result, "//") {
1170 result = u.Scheme + ":" + result
1171 }
1172 }
1173 if u.ForceQuery || u.RawQuery != "" {
1174 result += "?" + u.RawQuery
1175 }
1176 return result
1177 }
1178
1179
1180
1181
1182
1183 func (u *URL) Hostname() string {
1184 host, _ := splitHostPort(u.Host)
1185 return host
1186 }
1187
1188
1189
1190
1191 func (u *URL) Port() string {
1192 _, port := splitHostPort(u.Host)
1193 return port
1194 }
1195
1196
1197
1198
1199 func splitHostPort(hostPort string) (host, port string) {
1200 host = hostPort
1201
1202 colon := strings.LastIndexByte(host, ':')
1203 if colon != -1 && validOptionalPort(host[colon:]) {
1204 host, port = host[:colon], host[colon+1:]
1205 }
1206
1207 if strings.HasPrefix(host, "[") && strings.HasSuffix(host, "]") {
1208 host = host[1 : len(host)-1]
1209 }
1210
1211 return
1212 }
1213
1214
1215
1216
1217 func (u *URL) MarshalBinary() (text []byte, err error) {
1218 return u.AppendBinary(nil)
1219 }
1220
1221 func (u *URL) AppendBinary(b []byte) ([]byte, error) {
1222 return append(b, u.String()...), nil
1223 }
1224
1225 func (u *URL) UnmarshalBinary(text []byte) error {
1226 u1, err := Parse(string(text))
1227 if err != nil {
1228 return err
1229 }
1230 *u = *u1
1231 return nil
1232 }
1233
1234
1235
1236
1237
1238 func (u *URL) JoinPath(elem ...string) *URL {
1239 url, _ := u.joinPath(elem...)
1240 return url
1241 }
1242
1243 func (u *URL) joinPath(elem ...string) (*URL, error) {
1244 elem = append([]string{u.EscapedPath()}, elem...)
1245 var p string
1246 if !strings.HasPrefix(elem[0], "/") {
1247
1248
1249 elem[0] = "/" + elem[0]
1250 p = path.Join(elem...)[1:]
1251 } else {
1252 p = path.Join(elem...)
1253 }
1254
1255
1256 if strings.HasSuffix(elem[len(elem)-1], "/") && !strings.HasSuffix(p, "/") {
1257 p += "/"
1258 }
1259 url := *u
1260 err := url.setPath(p)
1261 return &url, err
1262 }
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273 func validUserinfo(s string) bool {
1274 for _, r := range s {
1275 if 'A' <= r && r <= 'Z' {
1276 continue
1277 }
1278 if 'a' <= r && r <= 'z' {
1279 continue
1280 }
1281 if '0' <= r && r <= '9' {
1282 continue
1283 }
1284 switch r {
1285 case '-', '.', '_', ':', '~', '!', '$', '&', '\'',
1286 '(', ')', '*', '+', ',', ';', '=', '%':
1287 continue
1288 case '@':
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298 continue
1299 default:
1300 return false
1301 }
1302 }
1303 return true
1304 }
1305
1306
1307 func stringContainsCTLByte(s string) bool {
1308 for i := 0; i < len(s); i++ {
1309 b := s[i]
1310 if b < ' ' || b == 0x7f {
1311 return true
1312 }
1313 }
1314 return false
1315 }
1316
1317
1318
1319
1320 func JoinPath(base string, elem ...string) (result string, err error) {
1321 url, err := Parse(base)
1322 if err != nil {
1323 return
1324 }
1325 res, err := url.joinPath(elem...)
1326 if err != nil {
1327 return "", err
1328 }
1329 return res.String(), nil
1330 }
1331
View as plain text