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