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