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