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 u.OmitHost && u.Host == "" && u.User == nil && strings.HasPrefix(path, "//") {
841
842
843
844 buf.WriteString("%2F")
845 path = path[1:]
846 }
847 if path != "" && path[0] != '/' && u.Host != "" {
848 buf.WriteByte('/')
849 }
850 if buf.Len() == 0 {
851
852
853
854
855
856
857 if segment, _, _ := strings.Cut(path, "/"); strings.Contains(segment, ":") {
858 buf.WriteString("./")
859 }
860 }
861 buf.WriteString(path)
862 }
863 if u.ForceQuery || u.RawQuery != "" {
864 buf.WriteByte('?')
865 buf.WriteString(u.RawQuery)
866 }
867 if u.Fragment != "" {
868 buf.WriteByte('#')
869 buf.WriteString(u.EscapedFragment())
870 }
871 return buf.String()
872 }
873
874
875
876 func (u *URL) Redacted() string {
877 if u == nil {
878 return ""
879 }
880
881 ru := *u
882 if _, has := ru.User.Password(); has {
883 ru.User = UserPassword(ru.User.Username(), "xxxxx")
884 }
885 return ru.String()
886 }
887
888
889
890
891
892 type Values map[string][]string
893
894
895
896
897
898 func (v Values) Get(key string) string {
899 vs := v[key]
900 if len(vs) == 0 {
901 return ""
902 }
903 return vs[0]
904 }
905
906
907
908 func (v Values) Set(key, value string) {
909 v[key] = []string{value}
910 }
911
912
913
914 func (v Values) Add(key, value string) {
915 v[key] = append(v[key], value)
916 }
917
918
919 func (v Values) Del(key string) {
920 delete(v, key)
921 }
922
923
924 func (v Values) Has(key string) bool {
925 _, ok := v[key]
926 return ok
927 }
928
929
930 func (vs Values) Clone() Values {
931 if vs == nil {
932 return nil
933 }
934
935 newVals := make(Values, len(vs))
936 for k, v := range vs {
937 newVals[k] = slices.Clone(v)
938 }
939 return newVals
940 }
941
942
943
944
945
946
947
948
949
950
951
952 func ParseQuery(query string) (Values, error) {
953 m := make(Values)
954 err := parseQuery(m, query)
955 return m, err
956 }
957
958 var urlmaxqueryparams = godebug.New("urlmaxqueryparams")
959
960
961 const defaultMaxParams = 10000
962
963 func urlParamsWithinMax(params int) bool {
964 withinDefaultMax := params <= defaultMaxParams
965 if urlmaxqueryparams.Value() == "" {
966 return withinDefaultMax
967 }
968 customMax, err := strconv.Atoi(urlmaxqueryparams.Value())
969 if err != nil {
970 return withinDefaultMax
971 }
972 withinCustomMax := customMax == 0 || params < customMax
973 if withinDefaultMax != withinCustomMax {
974 urlmaxqueryparams.IncNonDefault()
975 }
976 return withinCustomMax
977 }
978
979 func parseQuery(m Values, query string) (err error) {
980 if !urlParamsWithinMax(strings.Count(query, "&") + 1) {
981 return errors.New("number of URL query parameters exceeded limit")
982 }
983 for query != "" {
984 var key string
985 key, query, _ = strings.Cut(query, "&")
986 if strings.Contains(key, ";") {
987 err = fmt.Errorf("invalid semicolon separator in query")
988 continue
989 }
990 if key == "" {
991 continue
992 }
993 key, value, _ := strings.Cut(key, "=")
994 key, err1 := QueryUnescape(key)
995 if err1 != nil {
996 if err == nil {
997 err = err1
998 }
999 continue
1000 }
1001 value, err1 = QueryUnescape(value)
1002 if err1 != nil {
1003 if err == nil {
1004 err = err1
1005 }
1006 continue
1007 }
1008 m[key] = append(m[key], value)
1009 }
1010 return err
1011 }
1012
1013
1014
1015 func (v Values) Encode() string {
1016 if len(v) == 0 {
1017 return ""
1018 }
1019 var buf strings.Builder
1020
1021
1022 keys := make([]string, len(v))
1023 var i int
1024 for k := range v {
1025 keys[i] = k
1026 i++
1027 }
1028 slices.Sort(keys)
1029 for _, k := range keys {
1030 vs := v[k]
1031 keyEscaped := QueryEscape(k)
1032 for _, v := range vs {
1033 if buf.Len() > 0 {
1034 buf.WriteByte('&')
1035 }
1036 buf.WriteString(keyEscaped)
1037 buf.WriteByte('=')
1038 buf.WriteString(QueryEscape(v))
1039 }
1040 }
1041 return buf.String()
1042 }
1043
1044
1045
1046 func resolvePath(base, ref string) string {
1047 var full string
1048 if ref == "" {
1049 full = base
1050 } else if ref[0] != '/' {
1051 i := strings.LastIndex(base, "/")
1052 full = base[:i+1] + ref
1053 } else {
1054 full = ref
1055 }
1056 if full == "" {
1057 return ""
1058 }
1059
1060 dst := make([]byte, 0, len(full)+1)
1061 dst = append(dst, '/')
1062 elem := ""
1063 remaining := full
1064 found := true
1065 first := true
1066 for found {
1067 elem, remaining, found = strings.Cut(remaining, "/")
1068 switch elem {
1069 case ".":
1070 first = false
1071 continue
1072 case "..":
1073 if i := bytes.LastIndexByte(dst[1:], '/'); i >= 0 {
1074 dst = dst[:i+1]
1075 } else {
1076 dst = dst[:1]
1077 }
1078 first = len(dst) == 1
1079 default:
1080 if !first {
1081 dst = append(dst, '/')
1082 }
1083 dst = append(dst, elem...)
1084 first = false
1085 }
1086 }
1087
1088 if elem == "." || elem == ".." {
1089 dst = append(dst, '/')
1090 }
1091
1092
1093 if len(dst) > 1 && dst[1] == '/' {
1094 return string(dst[1:])
1095 }
1096 return string(dst)
1097 }
1098
1099
1100
1101 func (u *URL) IsAbs() bool {
1102 return u.Scheme != ""
1103 }
1104
1105
1106
1107
1108 func (u *URL) Parse(ref string) (*URL, error) {
1109 refURL, err := Parse(ref)
1110 if err != nil {
1111 return nil, err
1112 }
1113 return u.ResolveReference(refURL), nil
1114 }
1115
1116
1117
1118
1119
1120
1121
1122 func (u *URL) ResolveReference(ref *URL) *URL {
1123 url := *ref
1124 if ref.Scheme == "" {
1125 url.Scheme = u.Scheme
1126 }
1127 if ref.Scheme != "" || ref.Host != "" || ref.User != nil {
1128
1129
1130
1131 url.setPath(resolvePath(ref.EscapedPath(), ""))
1132 return &url
1133 }
1134 if ref.Opaque != "" {
1135 url.User = nil
1136 url.Host = ""
1137 url.Path = ""
1138 return &url
1139 }
1140 if ref.Path == "" && !ref.ForceQuery && ref.RawQuery == "" {
1141 url.RawQuery = u.RawQuery
1142 if ref.Fragment == "" {
1143 url.Fragment = u.Fragment
1144 url.RawFragment = u.RawFragment
1145 }
1146 }
1147 if ref.Path == "" && u.Opaque != "" {
1148 url.Opaque = u.Opaque
1149 url.User = nil
1150 url.Host = ""
1151 url.Path = ""
1152 return &url
1153 }
1154
1155 url.Host = u.Host
1156 url.User = u.User
1157 url.setPath(resolvePath(u.EscapedPath(), ref.EscapedPath()))
1158 return &url
1159 }
1160
1161
1162
1163
1164 func (u *URL) Query() Values {
1165 v, _ := ParseQuery(u.RawQuery)
1166 return v
1167 }
1168
1169
1170
1171 func (u *URL) RequestURI() string {
1172 result := u.Opaque
1173 if result == "" {
1174 result = u.EscapedPath()
1175 if result == "" {
1176 result = "/"
1177 }
1178 } else {
1179 if strings.HasPrefix(result, "//") {
1180 result = u.Scheme + ":" + result
1181 }
1182 }
1183 if u.ForceQuery || u.RawQuery != "" {
1184 result += "?" + u.RawQuery
1185 }
1186 return result
1187 }
1188
1189
1190
1191
1192
1193 func (u *URL) Hostname() string {
1194 host, _ := splitHostPort(u.Host)
1195 return host
1196 }
1197
1198
1199
1200
1201 func (u *URL) Port() string {
1202 _, port := splitHostPort(u.Host)
1203 return port
1204 }
1205
1206
1207
1208
1209 func splitHostPort(hostPort string) (host, port string) {
1210 host = hostPort
1211
1212 colon := strings.LastIndexByte(host, ':')
1213 if colon != -1 && validOptionalPort(host[colon:]) {
1214 host, port = host[:colon], host[colon+1:]
1215 }
1216
1217 if strings.HasPrefix(host, "[") && strings.HasSuffix(host, "]") {
1218 host = host[1 : len(host)-1]
1219 }
1220
1221 return
1222 }
1223
1224
1225
1226
1227 func (u *URL) MarshalBinary() (text []byte, err error) {
1228 return u.AppendBinary(nil)
1229 }
1230
1231 func (u *URL) AppendBinary(b []byte) ([]byte, error) {
1232 return append(b, u.String()...), nil
1233 }
1234
1235 func (u *URL) UnmarshalBinary(text []byte) error {
1236 u1, err := Parse(string(text))
1237 if err != nil {
1238 return err
1239 }
1240 *u = *u1
1241 return nil
1242 }
1243
1244
1245
1246
1247
1248 func (u *URL) JoinPath(elem ...string) *URL {
1249 url, _ := u.joinPath(elem...)
1250 return url
1251 }
1252
1253 func (u *URL) joinPath(elem ...string) (*URL, error) {
1254 elem = append([]string{u.EscapedPath()}, elem...)
1255 var p string
1256 if !strings.HasPrefix(elem[0], "/") {
1257
1258
1259 elem[0] = "/" + elem[0]
1260 p = path.Join(elem...)[1:]
1261 } else {
1262 p = path.Join(elem...)
1263 }
1264
1265
1266 if strings.HasSuffix(elem[len(elem)-1], "/") && !strings.HasSuffix(p, "/") {
1267 p += "/"
1268 }
1269 url := *u
1270 err := url.setPath(p)
1271 return &url, err
1272 }
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283 func validUserinfo(s string) bool {
1284 for _, r := range s {
1285 if 'A' <= r && r <= 'Z' {
1286 continue
1287 }
1288 if 'a' <= r && r <= 'z' {
1289 continue
1290 }
1291 if '0' <= r && r <= '9' {
1292 continue
1293 }
1294 switch r {
1295 case '-', '.', '_', ':', '~', '!', '$', '&', '\'',
1296 '(', ')', '*', '+', ',', ';', '=', '%':
1297 continue
1298 case '@':
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308 continue
1309 default:
1310 return false
1311 }
1312 }
1313 return true
1314 }
1315
1316
1317 func stringContainsCTLByte(s string) bool {
1318 for i := 0; i < len(s); i++ {
1319 b := s[i]
1320 if b < ' ' || b == 0x7f {
1321 return true
1322 }
1323 }
1324 return false
1325 }
1326
1327
1328
1329
1330 func JoinPath(base string, elem ...string) (result string, err error) {
1331 url, err := Parse(base)
1332 if err != nil {
1333 return
1334 }
1335 res, err := url.joinPath(elem...)
1336 if err != nil {
1337 return "", err
1338 }
1339 return res.String(), nil
1340 }
1341
1342
1343 func (u *URL) Clone() *URL {
1344 if u == nil {
1345 return nil
1346 }
1347
1348 uc := new(*u)
1349 if u.User != nil {
1350 uc.User = new(*u.User)
1351 }
1352 return uc
1353 }
1354
View as plain text