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