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