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