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