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 u.OmitHost && u.Host == "" && u.User == nil && strings.HasPrefix(path, "//") {
840
841
842
843 buf.WriteString("%2F")
844 path = path[1:]
845 }
846 if path != "" && path[0] != '/' && u.Host != "" {
847 buf.WriteByte('/')
848 }
849 if buf.Len() == 0 {
850
851
852
853
854
855
856 if segment, _, _ := strings.Cut(path, "/"); strings.Contains(segment, ":") {
857 buf.WriteString("./")
858 }
859 }
860 buf.WriteString(path)
861 }
862 if u.ForceQuery || u.RawQuery != "" {
863 buf.WriteByte('?')
864 buf.WriteString(u.RawQuery)
865 }
866 if u.Fragment != "" {
867 buf.WriteByte('#')
868 buf.WriteString(u.EscapedFragment())
869 }
870 return buf.String()
871 }
872
873
874
875 func (u *URL) Redacted() string {
876 if u == nil {
877 return ""
878 }
879
880 ru := *u
881 if _, has := ru.User.Password(); has {
882 ru.User = UserPassword(ru.User.Username(), "xxxxx")
883 }
884 return ru.String()
885 }
886
887
888
889
890
891 type Values map[string][]string
892
893
894
895
896
897 func (v Values) Get(key string) string {
898 vs := v[key]
899 if len(vs) == 0 {
900 return ""
901 }
902 return vs[0]
903 }
904
905
906
907 func (v Values) Set(key, value string) {
908 v[key] = []string{value}
909 }
910
911
912
913 func (v Values) Add(key, value string) {
914 v[key] = append(v[key], value)
915 }
916
917
918 func (v Values) Del(key string) {
919 delete(v, key)
920 }
921
922
923 func (v Values) Has(key string) bool {
924 _, ok := v[key]
925 return ok
926 }
927
928
929 func (vs Values) Clone() Values {
930 if vs == nil {
931 return nil
932 }
933
934 newVals := make(Values, len(vs))
935 for k, v := range vs {
936 newVals[k] = slices.Clone(v)
937 }
938 return newVals
939 }
940
941
942
943
944
945
946
947
948
949
950
951 func ParseQuery(query string) (Values, error) {
952 m := make(Values)
953 err := parseQuery(m, query)
954 return m, err
955 }
956
957 var urlmaxqueryparams = godebug.New("urlmaxqueryparams")
958
959 const defaultMaxParams = 10000
960
961 func urlParamsWithinMax(params int) bool {
962 withinDefaultMax := params <= defaultMaxParams
963 if urlmaxqueryparams.Value() == "" {
964 return withinDefaultMax
965 }
966 customMax, err := strconv.Atoi(urlmaxqueryparams.Value())
967 if err != nil {
968 return withinDefaultMax
969 }
970 withinCustomMax := customMax == 0 || params < customMax
971 if withinDefaultMax != withinCustomMax {
972 urlmaxqueryparams.IncNonDefault()
973 }
974 return withinCustomMax
975 }
976
977 func parseQuery(m Values, query string) (err error) {
978 if !urlParamsWithinMax(strings.Count(query, "&") + 1) {
979 return errors.New("number of URL query parameters exceeded limit")
980 }
981 for query != "" {
982 var key string
983 key, query, _ = strings.Cut(query, "&")
984 if strings.Contains(key, ";") {
985 err = fmt.Errorf("invalid semicolon separator in query")
986 continue
987 }
988 if key == "" {
989 continue
990 }
991 key, value, _ := strings.Cut(key, "=")
992 key, err1 := QueryUnescape(key)
993 if err1 != nil {
994 if err == nil {
995 err = err1
996 }
997 continue
998 }
999 value, err1 = QueryUnescape(value)
1000 if err1 != nil {
1001 if err == nil {
1002 err = err1
1003 }
1004 continue
1005 }
1006 m[key] = append(m[key], value)
1007 }
1008 return err
1009 }
1010
1011
1012
1013 func (v Values) Encode() string {
1014 if len(v) == 0 {
1015 return ""
1016 }
1017 var buf strings.Builder
1018
1019
1020 keys := make([]string, len(v))
1021 var i int
1022 for k := range v {
1023 keys[i] = k
1024 i++
1025 }
1026 slices.Sort(keys)
1027 for _, k := range keys {
1028 vs := v[k]
1029 keyEscaped := QueryEscape(k)
1030 for _, v := range vs {
1031 if buf.Len() > 0 {
1032 buf.WriteByte('&')
1033 }
1034 buf.WriteString(keyEscaped)
1035 buf.WriteByte('=')
1036 buf.WriteString(QueryEscape(v))
1037 }
1038 }
1039 return buf.String()
1040 }
1041
1042
1043
1044 func resolvePath(base, ref string) string {
1045 var full string
1046 if ref == "" {
1047 full = base
1048 } else if ref[0] != '/' {
1049 i := strings.LastIndex(base, "/")
1050 full = base[:i+1] + ref
1051 } else {
1052 full = ref
1053 }
1054 if full == "" {
1055 return ""
1056 }
1057
1058 var (
1059 elem string
1060 dst strings.Builder
1061 )
1062 first := true
1063 remaining := full
1064
1065 dst.WriteByte('/')
1066 found := true
1067 for found {
1068 elem, remaining, found = strings.Cut(remaining, "/")
1069 if elem == "." {
1070 first = false
1071
1072 continue
1073 }
1074
1075 if elem == ".." {
1076
1077 str := dst.String()[1:]
1078 index := strings.LastIndexByte(str, '/')
1079
1080 dst.Reset()
1081 dst.WriteByte('/')
1082 if index == -1 {
1083 first = true
1084 } else {
1085 dst.WriteString(str[:index])
1086 }
1087 } else {
1088 if !first {
1089 dst.WriteByte('/')
1090 }
1091 dst.WriteString(elem)
1092 first = false
1093 }
1094 }
1095
1096 if elem == "." || elem == ".." {
1097 dst.WriteByte('/')
1098 }
1099
1100
1101 r := dst.String()
1102 if len(r) > 1 && r[1] == '/' {
1103 r = r[1:]
1104 }
1105 return r
1106 }
1107
1108
1109
1110 func (u *URL) IsAbs() bool {
1111 return u.Scheme != ""
1112 }
1113
1114
1115
1116
1117 func (u *URL) Parse(ref string) (*URL, error) {
1118 refURL, err := Parse(ref)
1119 if err != nil {
1120 return nil, err
1121 }
1122 return u.ResolveReference(refURL), nil
1123 }
1124
1125
1126
1127
1128
1129
1130
1131 func (u *URL) ResolveReference(ref *URL) *URL {
1132 url := *ref
1133 if ref.Scheme == "" {
1134 url.Scheme = u.Scheme
1135 }
1136 if ref.Scheme != "" || ref.Host != "" || ref.User != nil {
1137
1138
1139
1140 url.setPath(resolvePath(ref.EscapedPath(), ""))
1141 return &url
1142 }
1143 if ref.Opaque != "" {
1144 url.User = nil
1145 url.Host = ""
1146 url.Path = ""
1147 return &url
1148 }
1149 if ref.Path == "" && !ref.ForceQuery && ref.RawQuery == "" {
1150 url.RawQuery = u.RawQuery
1151 if ref.Fragment == "" {
1152 url.Fragment = u.Fragment
1153 url.RawFragment = u.RawFragment
1154 }
1155 }
1156 if ref.Path == "" && u.Opaque != "" {
1157 url.Opaque = u.Opaque
1158 url.User = nil
1159 url.Host = ""
1160 url.Path = ""
1161 return &url
1162 }
1163
1164 url.Host = u.Host
1165 url.User = u.User
1166 url.setPath(resolvePath(u.EscapedPath(), ref.EscapedPath()))
1167 return &url
1168 }
1169
1170
1171
1172
1173 func (u *URL) Query() Values {
1174 v, _ := ParseQuery(u.RawQuery)
1175 return v
1176 }
1177
1178
1179
1180 func (u *URL) RequestURI() string {
1181 result := u.Opaque
1182 if result == "" {
1183 result = u.EscapedPath()
1184 if result == "" {
1185 result = "/"
1186 }
1187 } else {
1188 if strings.HasPrefix(result, "//") {
1189 result = u.Scheme + ":" + result
1190 }
1191 }
1192 if u.ForceQuery || u.RawQuery != "" {
1193 result += "?" + u.RawQuery
1194 }
1195 return result
1196 }
1197
1198
1199
1200
1201
1202 func (u *URL) Hostname() string {
1203 host, _ := splitHostPort(u.Host)
1204 return host
1205 }
1206
1207
1208
1209
1210 func (u *URL) Port() string {
1211 _, port := splitHostPort(u.Host)
1212 return port
1213 }
1214
1215
1216
1217
1218 func splitHostPort(hostPort string) (host, port string) {
1219 host = hostPort
1220
1221 colon := strings.LastIndexByte(host, ':')
1222 if colon != -1 && validOptionalPort(host[colon:]) {
1223 host, port = host[:colon], host[colon+1:]
1224 }
1225
1226 if strings.HasPrefix(host, "[") && strings.HasSuffix(host, "]") {
1227 host = host[1 : len(host)-1]
1228 }
1229
1230 return
1231 }
1232
1233
1234
1235
1236 func (u *URL) MarshalBinary() (text []byte, err error) {
1237 return u.AppendBinary(nil)
1238 }
1239
1240 func (u *URL) AppendBinary(b []byte) ([]byte, error) {
1241 return append(b, u.String()...), nil
1242 }
1243
1244 func (u *URL) UnmarshalBinary(text []byte) error {
1245 u1, err := Parse(string(text))
1246 if err != nil {
1247 return err
1248 }
1249 *u = *u1
1250 return nil
1251 }
1252
1253
1254
1255
1256
1257 func (u *URL) JoinPath(elem ...string) *URL {
1258 url, _ := u.joinPath(elem...)
1259 return url
1260 }
1261
1262 func (u *URL) joinPath(elem ...string) (*URL, error) {
1263 elem = append([]string{u.EscapedPath()}, elem...)
1264 var p string
1265 if !strings.HasPrefix(elem[0], "/") {
1266
1267
1268 elem[0] = "/" + elem[0]
1269 p = path.Join(elem...)[1:]
1270 } else {
1271 p = path.Join(elem...)
1272 }
1273
1274
1275 if strings.HasSuffix(elem[len(elem)-1], "/") && !strings.HasSuffix(p, "/") {
1276 p += "/"
1277 }
1278 url := *u
1279 err := url.setPath(p)
1280 return &url, err
1281 }
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292 func validUserinfo(s string) bool {
1293 for _, r := range s {
1294 if 'A' <= r && r <= 'Z' {
1295 continue
1296 }
1297 if 'a' <= r && r <= 'z' {
1298 continue
1299 }
1300 if '0' <= r && r <= '9' {
1301 continue
1302 }
1303 switch r {
1304 case '-', '.', '_', ':', '~', '!', '$', '&', '\'',
1305 '(', ')', '*', '+', ',', ';', '=', '%':
1306 continue
1307 case '@':
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317 continue
1318 default:
1319 return false
1320 }
1321 }
1322 return true
1323 }
1324
1325
1326 func stringContainsCTLByte(s string) bool {
1327 for i := 0; i < len(s); i++ {
1328 b := s[i]
1329 if b < ' ' || b == 0x7f {
1330 return true
1331 }
1332 }
1333 return false
1334 }
1335
1336
1337
1338
1339 func JoinPath(base string, elem ...string) (result string, err error) {
1340 url, err := Parse(base)
1341 if err != nil {
1342 return
1343 }
1344 res, err := url.joinPath(elem...)
1345 if err != nil {
1346 return "", err
1347 }
1348 return res.String(), nil
1349 }
1350
1351
1352 func (u *URL) Clone() *URL {
1353 if u == nil {
1354 return nil
1355 }
1356
1357 uc := new(*u)
1358 if u.User != nil {
1359 uc.User = new(*u.User)
1360 }
1361 return uc
1362 }
1363
View as plain text