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