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