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