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 "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 if u.Scheme != "" {
818 buf.WriteString(u.Scheme)
819 buf.WriteByte(':')
820 }
821 if u.Opaque != "" {
822 buf.WriteString(u.Opaque)
823 } else {
824 if u.Scheme != "" || u.Host != "" || u.User != nil {
825 if u.OmitHost && u.Host == "" && u.User == nil {
826
827 } else {
828 if u.Host != "" || u.Path != "" || u.User != nil {
829 buf.WriteString("//")
830 }
831 if ui := u.User; ui != nil {
832 buf.WriteString(ui.String())
833 buf.WriteByte('@')
834 }
835 if h := u.Host; h != "" {
836 buf.WriteString(escape(h, encodeHost))
837 }
838 }
839 }
840 path := u.EscapedPath()
841 if path != "" && path[0] != '/' && u.Host != "" {
842 buf.WriteByte('/')
843 }
844 if buf.Len() == 0 {
845
846
847
848
849
850
851 if segment, _, _ := strings.Cut(path, "/"); strings.Contains(segment, ":") {
852 buf.WriteString("./")
853 }
854 }
855 buf.WriteString(path)
856 }
857 if u.ForceQuery || u.RawQuery != "" {
858 buf.WriteByte('?')
859 buf.WriteString(u.RawQuery)
860 }
861 if u.Fragment != "" {
862 buf.WriteByte('#')
863 buf.WriteString(u.EscapedFragment())
864 }
865 return buf.String()
866 }
867
868
869
870 func (u *URL) Redacted() string {
871 if u == nil {
872 return ""
873 }
874
875 ru := *u
876 if _, has := ru.User.Password(); has {
877 ru.User = UserPassword(ru.User.Username(), "xxxxx")
878 }
879 return ru.String()
880 }
881
882
883
884
885
886 type Values map[string][]string
887
888
889
890
891
892 func (v Values) Get(key string) string {
893 vs := v[key]
894 if len(vs) == 0 {
895 return ""
896 }
897 return vs[0]
898 }
899
900
901
902 func (v Values) Set(key, value string) {
903 v[key] = []string{value}
904 }
905
906
907
908 func (v Values) Add(key, value string) {
909 v[key] = append(v[key], value)
910 }
911
912
913 func (v Values) Del(key string) {
914 delete(v, key)
915 }
916
917
918 func (v Values) Has(key string) bool {
919 _, ok := v[key]
920 return ok
921 }
922
923
924
925
926
927
928
929
930
931
932
933 func ParseQuery(query string) (Values, error) {
934 m := make(Values)
935 err := parseQuery(m, query)
936 return m, err
937 }
938
939 func parseQuery(m Values, query string) (err error) {
940 for query != "" {
941 var key string
942 key, query, _ = strings.Cut(query, "&")
943 if strings.Contains(key, ";") {
944 err = fmt.Errorf("invalid semicolon separator in query")
945 continue
946 }
947 if key == "" {
948 continue
949 }
950 key, value, _ := strings.Cut(key, "=")
951 key, err1 := QueryUnescape(key)
952 if err1 != nil {
953 if err == nil {
954 err = err1
955 }
956 continue
957 }
958 value, err1 = QueryUnescape(value)
959 if err1 != nil {
960 if err == nil {
961 err = err1
962 }
963 continue
964 }
965 m[key] = append(m[key], value)
966 }
967 return err
968 }
969
970
971
972 func (v Values) Encode() string {
973 if len(v) == 0 {
974 return ""
975 }
976 var buf strings.Builder
977 keys := make([]string, 0, len(v))
978 for k := range v {
979 keys = append(keys, k)
980 }
981 sort.Strings(keys)
982 for _, k := range keys {
983 vs := v[k]
984 keyEscaped := QueryEscape(k)
985 for _, v := range vs {
986 if buf.Len() > 0 {
987 buf.WriteByte('&')
988 }
989 buf.WriteString(keyEscaped)
990 buf.WriteByte('=')
991 buf.WriteString(QueryEscape(v))
992 }
993 }
994 return buf.String()
995 }
996
997
998
999 func resolvePath(base, ref string) string {
1000 var full string
1001 if ref == "" {
1002 full = base
1003 } else if ref[0] != '/' {
1004 i := strings.LastIndex(base, "/")
1005 full = base[:i+1] + ref
1006 } else {
1007 full = ref
1008 }
1009 if full == "" {
1010 return ""
1011 }
1012
1013 var (
1014 elem string
1015 dst strings.Builder
1016 )
1017 first := true
1018 remaining := full
1019
1020 dst.WriteByte('/')
1021 found := true
1022 for found {
1023 elem, remaining, found = strings.Cut(remaining, "/")
1024 if elem == "." {
1025 first = false
1026
1027 continue
1028 }
1029
1030 if elem == ".." {
1031
1032 str := dst.String()[1:]
1033 index := strings.LastIndexByte(str, '/')
1034
1035 dst.Reset()
1036 dst.WriteByte('/')
1037 if index == -1 {
1038 first = true
1039 } else {
1040 dst.WriteString(str[:index])
1041 }
1042 } else {
1043 if !first {
1044 dst.WriteByte('/')
1045 }
1046 dst.WriteString(elem)
1047 first = false
1048 }
1049 }
1050
1051 if elem == "." || elem == ".." {
1052 dst.WriteByte('/')
1053 }
1054
1055
1056 r := dst.String()
1057 if len(r) > 1 && r[1] == '/' {
1058 r = r[1:]
1059 }
1060 return r
1061 }
1062
1063
1064
1065 func (u *URL) IsAbs() bool {
1066 return u.Scheme != ""
1067 }
1068
1069
1070
1071
1072 func (u *URL) Parse(ref string) (*URL, error) {
1073 refURL, err := Parse(ref)
1074 if err != nil {
1075 return nil, err
1076 }
1077 return u.ResolveReference(refURL), nil
1078 }
1079
1080
1081
1082
1083
1084
1085
1086 func (u *URL) ResolveReference(ref *URL) *URL {
1087 url := *ref
1088 if ref.Scheme == "" {
1089 url.Scheme = u.Scheme
1090 }
1091 if ref.Scheme != "" || ref.Host != "" || ref.User != nil {
1092
1093
1094
1095 url.setPath(resolvePath(ref.EscapedPath(), ""))
1096 return &url
1097 }
1098 if ref.Opaque != "" {
1099 url.User = nil
1100 url.Host = ""
1101 url.Path = ""
1102 return &url
1103 }
1104 if ref.Path == "" && !ref.ForceQuery && ref.RawQuery == "" {
1105 url.RawQuery = u.RawQuery
1106 if ref.Fragment == "" {
1107 url.Fragment = u.Fragment
1108 url.RawFragment = u.RawFragment
1109 }
1110 }
1111
1112 url.Host = u.Host
1113 url.User = u.User
1114 url.setPath(resolvePath(u.EscapedPath(), ref.EscapedPath()))
1115 return &url
1116 }
1117
1118
1119
1120
1121 func (u *URL) Query() Values {
1122 v, _ := ParseQuery(u.RawQuery)
1123 return v
1124 }
1125
1126
1127
1128 func (u *URL) RequestURI() string {
1129 result := u.Opaque
1130 if result == "" {
1131 result = u.EscapedPath()
1132 if result == "" {
1133 result = "/"
1134 }
1135 } else {
1136 if strings.HasPrefix(result, "//") {
1137 result = u.Scheme + ":" + result
1138 }
1139 }
1140 if u.ForceQuery || u.RawQuery != "" {
1141 result += "?" + u.RawQuery
1142 }
1143 return result
1144 }
1145
1146
1147
1148
1149
1150 func (u *URL) Hostname() string {
1151 host, _ := splitHostPort(u.Host)
1152 return host
1153 }
1154
1155
1156
1157
1158 func (u *URL) Port() string {
1159 _, port := splitHostPort(u.Host)
1160 return port
1161 }
1162
1163
1164
1165
1166 func splitHostPort(hostPort string) (host, port string) {
1167 host = hostPort
1168
1169 colon := strings.LastIndexByte(host, ':')
1170 if colon != -1 && validOptionalPort(host[colon:]) {
1171 host, port = host[:colon], host[colon+1:]
1172 }
1173
1174 if strings.HasPrefix(host, "[") && strings.HasSuffix(host, "]") {
1175 host = host[1 : len(host)-1]
1176 }
1177
1178 return
1179 }
1180
1181
1182
1183
1184 func (u *URL) MarshalBinary() (text []byte, err error) {
1185 return []byte(u.String()), nil
1186 }
1187
1188 func (u *URL) UnmarshalBinary(text []byte) error {
1189 u1, err := Parse(string(text))
1190 if err != nil {
1191 return err
1192 }
1193 *u = *u1
1194 return nil
1195 }
1196
1197
1198
1199
1200 func (u *URL) JoinPath(elem ...string) *URL {
1201 elem = append([]string{u.EscapedPath()}, elem...)
1202 var p string
1203 if !strings.HasPrefix(elem[0], "/") {
1204
1205
1206 elem[0] = "/" + elem[0]
1207 p = path.Join(elem...)[1:]
1208 } else {
1209 p = path.Join(elem...)
1210 }
1211
1212
1213 if strings.HasSuffix(elem[len(elem)-1], "/") && !strings.HasSuffix(p, "/") {
1214 p += "/"
1215 }
1216 url := *u
1217 url.setPath(p)
1218 return &url
1219 }
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230 func validUserinfo(s string) bool {
1231 for _, r := range s {
1232 if 'A' <= r && r <= 'Z' {
1233 continue
1234 }
1235 if 'a' <= r && r <= 'z' {
1236 continue
1237 }
1238 if '0' <= r && r <= '9' {
1239 continue
1240 }
1241 switch r {
1242 case '-', '.', '_', ':', '~', '!', '$', '&', '\'',
1243 '(', ')', '*', '+', ',', ';', '=', '%', '@':
1244 continue
1245 default:
1246 return false
1247 }
1248 }
1249 return true
1250 }
1251
1252
1253 func stringContainsCTLByte(s string) bool {
1254 for i := 0; i < len(s); i++ {
1255 b := s[i]
1256 if b < ' ' || b == 0x7f {
1257 return true
1258 }
1259 }
1260 return false
1261 }
1262
1263
1264
1265 func JoinPath(base string, elem ...string) (result string, err error) {
1266 url, err := Parse(base)
1267 if err != nil {
1268 return
1269 }
1270 result = url.JoinPath(elem...).String()
1271 return
1272 }
1273
View as plain text