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