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