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