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 != -1 {
551
552
553 closeBracketIdx := strings.LastIndex(host, "]")
554 if closeBracketIdx < 0 {
555 return "", errors.New("missing ']' in host")
556 }
557
558 colonPort := host[closeBracketIdx+1:]
559 if !validOptionalPort(colonPort) {
560 return "", fmt.Errorf("invalid port %q after host", colonPort)
561 }
562 unescapedColonPort, err := unescape(colonPort, encodeHost)
563 if err != nil {
564 return "", err
565 }
566
567 hostname := host[openBracketIdx+1 : closeBracketIdx]
568 var unescapedHostname string
569
570
571
572
573
574
575 zoneIdx := strings.Index(hostname, "%25")
576 if zoneIdx >= 0 {
577 hostPart, err := unescape(hostname[:zoneIdx], encodeHost)
578 if err != nil {
579 return "", err
580 }
581 zonePart, err := unescape(hostname[zoneIdx:], encodeZone)
582 if err != nil {
583 return "", err
584 }
585 unescapedHostname = hostPart + zonePart
586 } else {
587 var err error
588 unescapedHostname, err = unescape(hostname, encodeHost)
589 if err != nil {
590 return "", err
591 }
592 }
593
594
595
596
597 addr, err := netip.ParseAddr(unescapedHostname)
598 if err != nil {
599 return "", fmt.Errorf("invalid host: %w", err)
600 }
601 if addr.Is4() {
602 return "", errors.New("invalid IP-literal")
603 }
604 return "[" + unescapedHostname + "]" + unescapedColonPort, nil
605 } else if i := strings.Index(host, ":"); i != -1 {
606 lastColon := strings.LastIndex(host, ":")
607 if lastColon != i {
608 if scheme == "postgresql" || scheme == "postgres" {
609
610
611
612
613
614
615
616
617 i = lastColon
618 } else if urlstrictcolons.Value() == "0" {
619 urlstrictcolons.IncNonDefault()
620 i = lastColon
621 }
622 }
623 colonPort := host[i:]
624 if !validOptionalPort(colonPort) {
625 return "", fmt.Errorf("invalid port %q after host", colonPort)
626 }
627 }
628
629 var err error
630 if host, err = unescape(host, encodeHost); err != nil {
631 return "", err
632 }
633 return host, nil
634 }
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654 func (u *URL) setPath(p string) error {
655 path, err := unescape(p, encodePath)
656 if err != nil {
657 return err
658 }
659 u.Path = path
660 if escp := escape(path, encodePath); p == escp {
661
662 u.RawPath = ""
663 } else {
664 u.RawPath = p
665 }
666 return nil
667 }
668
669
670 func badSetPath(*URL, string) error
671
672
673
674
675
676
677
678
679
680
681 func (u *URL) EscapedPath() string {
682 if u.RawPath != "" && validEncoded(u.RawPath, encodePath) {
683 p, err := unescape(u.RawPath, encodePath)
684 if err == nil && p == u.Path {
685 return u.RawPath
686 }
687 }
688 if u.Path == "*" {
689 return "*"
690 }
691 return escape(u.Path, encodePath)
692 }
693
694
695
696
697 func validEncoded(s string, mode encoding) bool {
698 for i := 0; i < len(s); i++ {
699
700
701
702
703
704 switch s[i] {
705 case '!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=', ':', '@':
706
707 case '[', ']':
708
709 case '%':
710
711 default:
712 if shouldEscape(s[i], mode) {
713 return false
714 }
715 }
716 }
717 return true
718 }
719
720
721 func (u *URL) setFragment(f string) error {
722 frag, err := unescape(f, encodeFragment)
723 if err != nil {
724 return err
725 }
726 u.Fragment = frag
727 if escf := escape(frag, encodeFragment); f == escf {
728
729 u.RawFragment = ""
730 } else {
731 u.RawFragment = f
732 }
733 return nil
734 }
735
736
737
738
739
740
741
742
743
744 func (u *URL) EscapedFragment() string {
745 if u.RawFragment != "" && validEncoded(u.RawFragment, encodeFragment) {
746 f, err := unescape(u.RawFragment, encodeFragment)
747 if err == nil && f == u.Fragment {
748 return u.RawFragment
749 }
750 }
751 return escape(u.Fragment, encodeFragment)
752 }
753
754
755
756 func validOptionalPort(port string) bool {
757 if port == "" {
758 return true
759 }
760 if port[0] != ':' {
761 return false
762 }
763 for _, b := range port[1:] {
764 if b < '0' || b > '9' {
765 return false
766 }
767 }
768 return true
769 }
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792 func (u *URL) String() string {
793 var buf strings.Builder
794
795 n := len(u.Scheme)
796 if u.Opaque != "" {
797 n += len(u.Opaque)
798 } else {
799 if !u.OmitHost && (u.Scheme != "" || u.Host != "" || u.User != nil) {
800 username := u.User.Username()
801 password, _ := u.User.Password()
802 n += len(username) + len(password) + len(u.Host)
803 }
804 n += len(u.Path)
805 }
806 n += len(u.RawQuery) + len(u.RawFragment)
807 n += len(":" + "//" + "//" + ":" + "@" + "/" + "./" + "?" + "#")
808 buf.Grow(n)
809
810 if u.Scheme != "" {
811 buf.WriteString(u.Scheme)
812 buf.WriteByte(':')
813 }
814 if u.Opaque != "" {
815 buf.WriteString(u.Opaque)
816 } else {
817 if u.Scheme != "" || u.Host != "" || u.User != nil {
818 if u.OmitHost && u.Host == "" && u.User == nil {
819
820 } else {
821 if u.Host != "" || u.Path != "" || u.User != nil {
822 buf.WriteString("//")
823 }
824 if ui := u.User; ui != nil {
825 buf.WriteString(ui.String())
826 buf.WriteByte('@')
827 }
828 if h := u.Host; h != "" {
829 buf.WriteString(escape(h, encodeHost))
830 }
831 }
832 }
833 path := u.EscapedPath()
834 if path != "" && path[0] != '/' && u.Host != "" {
835 buf.WriteByte('/')
836 }
837 if buf.Len() == 0 {
838
839
840
841
842
843
844 if segment, _, _ := strings.Cut(path, "/"); strings.Contains(segment, ":") {
845 buf.WriteString("./")
846 }
847 }
848 buf.WriteString(path)
849 }
850 if u.ForceQuery || u.RawQuery != "" {
851 buf.WriteByte('?')
852 buf.WriteString(u.RawQuery)
853 }
854 if u.Fragment != "" {
855 buf.WriteByte('#')
856 buf.WriteString(u.EscapedFragment())
857 }
858 return buf.String()
859 }
860
861
862
863 func (u *URL) Redacted() string {
864 if u == nil {
865 return ""
866 }
867
868 ru := *u
869 if _, has := ru.User.Password(); has {
870 ru.User = UserPassword(ru.User.Username(), "xxxxx")
871 }
872 return ru.String()
873 }
874
875
876
877
878
879 type Values map[string][]string
880
881
882
883
884
885 func (v Values) Get(key string) string {
886 vs := v[key]
887 if len(vs) == 0 {
888 return ""
889 }
890 return vs[0]
891 }
892
893
894
895 func (v Values) Set(key, value string) {
896 v[key] = []string{value}
897 }
898
899
900
901 func (v Values) Add(key, value string) {
902 v[key] = append(v[key], value)
903 }
904
905
906 func (v Values) Del(key string) {
907 delete(v, key)
908 }
909
910
911 func (v Values) Has(key string) bool {
912 _, ok := v[key]
913 return ok
914 }
915
916
917
918
919
920
921
922
923
924
925
926 func ParseQuery(query string) (Values, error) {
927 m := make(Values)
928 err := parseQuery(m, query)
929 return m, err
930 }
931
932 var urlmaxqueryparams = godebug.New("urlmaxqueryparams")
933
934 const defaultMaxParams = 10000
935
936 func urlParamsWithinMax(params int) bool {
937 withinDefaultMax := params <= defaultMaxParams
938 if urlmaxqueryparams.Value() == "" {
939 return withinDefaultMax
940 }
941 customMax, err := strconv.Atoi(urlmaxqueryparams.Value())
942 if err != nil {
943 return withinDefaultMax
944 }
945 withinCustomMax := customMax == 0 || params < customMax
946 if withinDefaultMax != withinCustomMax {
947 urlmaxqueryparams.IncNonDefault()
948 }
949 return withinCustomMax
950 }
951
952 func parseQuery(m Values, query string) (err error) {
953 if !urlParamsWithinMax(strings.Count(query, "&") + 1) {
954 return errors.New("number of URL query parameters exceeded limit")
955 }
956 for query != "" {
957 var key string
958 key, query, _ = strings.Cut(query, "&")
959 if strings.Contains(key, ";") {
960 err = fmt.Errorf("invalid semicolon separator in query")
961 continue
962 }
963 if key == "" {
964 continue
965 }
966 key, value, _ := strings.Cut(key, "=")
967 key, err1 := QueryUnescape(key)
968 if err1 != nil {
969 if err == nil {
970 err = err1
971 }
972 continue
973 }
974 value, err1 = QueryUnescape(value)
975 if err1 != nil {
976 if err == nil {
977 err = err1
978 }
979 continue
980 }
981 m[key] = append(m[key], value)
982 }
983 return err
984 }
985
986
987
988 func (v Values) Encode() string {
989 if len(v) == 0 {
990 return ""
991 }
992 var buf strings.Builder
993
994
995 keys := make([]string, len(v))
996 var i int
997 for k := range v {
998 keys[i] = k
999 i++
1000 }
1001 slices.Sort(keys)
1002 for _, k := range keys {
1003 vs := v[k]
1004 keyEscaped := QueryEscape(k)
1005 for _, v := range vs {
1006 if buf.Len() > 0 {
1007 buf.WriteByte('&')
1008 }
1009 buf.WriteString(keyEscaped)
1010 buf.WriteByte('=')
1011 buf.WriteString(QueryEscape(v))
1012 }
1013 }
1014 return buf.String()
1015 }
1016
1017
1018
1019 func resolvePath(base, ref string) string {
1020 var full string
1021 if ref == "" {
1022 full = base
1023 } else if ref[0] != '/' {
1024 i := strings.LastIndex(base, "/")
1025 full = base[:i+1] + ref
1026 } else {
1027 full = ref
1028 }
1029 if full == "" {
1030 return ""
1031 }
1032
1033 var (
1034 elem string
1035 dst strings.Builder
1036 )
1037 first := true
1038 remaining := full
1039
1040 dst.WriteByte('/')
1041 found := true
1042 for found {
1043 elem, remaining, found = strings.Cut(remaining, "/")
1044 if elem == "." {
1045 first = false
1046
1047 continue
1048 }
1049
1050 if elem == ".." {
1051
1052 str := dst.String()[1:]
1053 index := strings.LastIndexByte(str, '/')
1054
1055 dst.Reset()
1056 dst.WriteByte('/')
1057 if index == -1 {
1058 first = true
1059 } else {
1060 dst.WriteString(str[:index])
1061 }
1062 } else {
1063 if !first {
1064 dst.WriteByte('/')
1065 }
1066 dst.WriteString(elem)
1067 first = false
1068 }
1069 }
1070
1071 if elem == "." || elem == ".." {
1072 dst.WriteByte('/')
1073 }
1074
1075
1076 r := dst.String()
1077 if len(r) > 1 && r[1] == '/' {
1078 r = r[1:]
1079 }
1080 return r
1081 }
1082
1083
1084
1085 func (u *URL) IsAbs() bool {
1086 return u.Scheme != ""
1087 }
1088
1089
1090
1091
1092 func (u *URL) Parse(ref string) (*URL, error) {
1093 refURL, err := Parse(ref)
1094 if err != nil {
1095 return nil, err
1096 }
1097 return u.ResolveReference(refURL), nil
1098 }
1099
1100
1101
1102
1103
1104
1105
1106 func (u *URL) ResolveReference(ref *URL) *URL {
1107 url := *ref
1108 if ref.Scheme == "" {
1109 url.Scheme = u.Scheme
1110 }
1111 if ref.Scheme != "" || ref.Host != "" || ref.User != nil {
1112
1113
1114
1115 url.setPath(resolvePath(ref.EscapedPath(), ""))
1116 return &url
1117 }
1118 if ref.Opaque != "" {
1119 url.User = nil
1120 url.Host = ""
1121 url.Path = ""
1122 return &url
1123 }
1124 if ref.Path == "" && !ref.ForceQuery && ref.RawQuery == "" {
1125 url.RawQuery = u.RawQuery
1126 if ref.Fragment == "" {
1127 url.Fragment = u.Fragment
1128 url.RawFragment = u.RawFragment
1129 }
1130 }
1131 if ref.Path == "" && u.Opaque != "" {
1132 url.Opaque = u.Opaque
1133 url.User = nil
1134 url.Host = ""
1135 url.Path = ""
1136 return &url
1137 }
1138
1139 url.Host = u.Host
1140 url.User = u.User
1141 url.setPath(resolvePath(u.EscapedPath(), ref.EscapedPath()))
1142 return &url
1143 }
1144
1145
1146
1147
1148 func (u *URL) Query() Values {
1149 v, _ := ParseQuery(u.RawQuery)
1150 return v
1151 }
1152
1153
1154
1155 func (u *URL) RequestURI() string {
1156 result := u.Opaque
1157 if result == "" {
1158 result = u.EscapedPath()
1159 if result == "" {
1160 result = "/"
1161 }
1162 } else {
1163 if strings.HasPrefix(result, "//") {
1164 result = u.Scheme + ":" + result
1165 }
1166 }
1167 if u.ForceQuery || u.RawQuery != "" {
1168 result += "?" + u.RawQuery
1169 }
1170 return result
1171 }
1172
1173
1174
1175
1176
1177 func (u *URL) Hostname() string {
1178 host, _ := splitHostPort(u.Host)
1179 return host
1180 }
1181
1182
1183
1184
1185 func (u *URL) Port() string {
1186 _, port := splitHostPort(u.Host)
1187 return port
1188 }
1189
1190
1191
1192
1193 func splitHostPort(hostPort string) (host, port string) {
1194 host = hostPort
1195
1196 colon := strings.LastIndexByte(host, ':')
1197 if colon != -1 && validOptionalPort(host[colon:]) {
1198 host, port = host[:colon], host[colon+1:]
1199 }
1200
1201 if strings.HasPrefix(host, "[") && strings.HasSuffix(host, "]") {
1202 host = host[1 : len(host)-1]
1203 }
1204
1205 return
1206 }
1207
1208
1209
1210
1211 func (u *URL) MarshalBinary() (text []byte, err error) {
1212 return u.AppendBinary(nil)
1213 }
1214
1215 func (u *URL) AppendBinary(b []byte) ([]byte, error) {
1216 return append(b, u.String()...), nil
1217 }
1218
1219 func (u *URL) UnmarshalBinary(text []byte) error {
1220 u1, err := Parse(string(text))
1221 if err != nil {
1222 return err
1223 }
1224 *u = *u1
1225 return nil
1226 }
1227
1228
1229
1230
1231
1232 func (u *URL) JoinPath(elem ...string) *URL {
1233 url, _ := u.joinPath(elem...)
1234 return url
1235 }
1236
1237 func (u *URL) joinPath(elem ...string) (*URL, error) {
1238 elem = append([]string{u.EscapedPath()}, elem...)
1239 var p string
1240 if !strings.HasPrefix(elem[0], "/") {
1241
1242
1243 elem[0] = "/" + elem[0]
1244 p = path.Join(elem...)[1:]
1245 } else {
1246 p = path.Join(elem...)
1247 }
1248
1249
1250 if strings.HasSuffix(elem[len(elem)-1], "/") && !strings.HasSuffix(p, "/") {
1251 p += "/"
1252 }
1253 url := *u
1254 err := url.setPath(p)
1255 return &url, err
1256 }
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267 func validUserinfo(s string) bool {
1268 for _, r := range s {
1269 if 'A' <= r && r <= 'Z' {
1270 continue
1271 }
1272 if 'a' <= r && r <= 'z' {
1273 continue
1274 }
1275 if '0' <= r && r <= '9' {
1276 continue
1277 }
1278 switch r {
1279 case '-', '.', '_', ':', '~', '!', '$', '&', '\'',
1280 '(', ')', '*', '+', ',', ';', '=', '%':
1281 continue
1282 case '@':
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292 continue
1293 default:
1294 return false
1295 }
1296 }
1297 return true
1298 }
1299
1300
1301 func stringContainsCTLByte(s string) bool {
1302 for i := 0; i < len(s); i++ {
1303 b := s[i]
1304 if b < ' ' || b == 0x7f {
1305 return true
1306 }
1307 }
1308 return false
1309 }
1310
1311
1312
1313
1314 func JoinPath(base string, elem ...string) (result string, err error) {
1315 url, err := Parse(base)
1316 if err != nil {
1317 return
1318 }
1319 res, err := url.joinPath(elem...)
1320 if err != nil {
1321 return "", err
1322 }
1323 return res.String(), nil
1324 }
1325
View as plain text