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