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 > 0 {
632 return "", errors.New("invalid IP-literal")
633 } else if openBracketIdx == 0 {
634
635
636 closeBracketIdx := strings.LastIndex(host, "]")
637 if closeBracketIdx < 0 {
638 return "", errors.New("missing ']' in host")
639 }
640
641 colonPort := host[closeBracketIdx+1:]
642 if !validOptionalPort(colonPort) {
643 return "", fmt.Errorf("invalid port %q after host", colonPort)
644 }
645 unescapedColonPort, err := unescape(colonPort, encodeHost)
646 if err != nil {
647 return "", err
648 }
649
650 hostname := host[openBracketIdx+1 : closeBracketIdx]
651 var unescapedHostname string
652
653
654
655
656
657
658 zoneIdx := strings.Index(hostname, "%25")
659 if zoneIdx >= 0 {
660 hostPart, err := unescape(hostname[:zoneIdx], encodeHost)
661 if err != nil {
662 return "", err
663 }
664 zonePart, err := unescape(hostname[zoneIdx:], encodeZone)
665 if err != nil {
666 return "", err
667 }
668 unescapedHostname = hostPart + zonePart
669 } else {
670 var err error
671 unescapedHostname, err = unescape(hostname, encodeHost)
672 if err != nil {
673 return "", err
674 }
675 }
676
677
678
679
680 addr, err := netip.ParseAddr(unescapedHostname)
681 if err != nil {
682 return "", fmt.Errorf("invalid host: %w", err)
683 }
684 if addr.Is4() {
685 return "", errors.New("invalid IP-literal")
686 }
687 return "[" + unescapedHostname + "]" + unescapedColonPort, nil
688 } else if i := strings.LastIndex(host, ":"); i != -1 {
689 colonPort := host[i:]
690 if !validOptionalPort(colonPort) {
691 return "", fmt.Errorf("invalid port %q after host", colonPort)
692 }
693 }
694
695 var err error
696 if host, err = unescape(host, encodeHost); err != nil {
697 return "", err
698 }
699 return host, nil
700 }
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720 func (u *URL) setPath(p string) error {
721 path, err := unescape(p, encodePath)
722 if err != nil {
723 return err
724 }
725 u.Path = path
726 if escp := escape(path, encodePath); p == escp {
727
728 u.RawPath = ""
729 } else {
730 u.RawPath = p
731 }
732 return nil
733 }
734
735
736 func badSetPath(*URL, string) error
737
738
739
740
741
742
743
744
745
746
747 func (u *URL) EscapedPath() string {
748 if u.RawPath != "" && validEncoded(u.RawPath, encodePath) {
749 p, err := unescape(u.RawPath, encodePath)
750 if err == nil && p == u.Path {
751 return u.RawPath
752 }
753 }
754 if u.Path == "*" {
755 return "*"
756 }
757 return escape(u.Path, encodePath)
758 }
759
760
761
762
763 func validEncoded(s string, mode encoding) bool {
764 for i := 0; i < len(s); i++ {
765
766
767
768
769
770 switch s[i] {
771 case '!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=', ':', '@':
772
773 case '[', ']':
774
775 case '%':
776
777 default:
778 if shouldEscape(s[i], mode) {
779 return false
780 }
781 }
782 }
783 return true
784 }
785
786
787 func (u *URL) setFragment(f string) error {
788 frag, err := unescape(f, encodeFragment)
789 if err != nil {
790 return err
791 }
792 u.Fragment = frag
793 if escf := escape(frag, encodeFragment); f == escf {
794
795 u.RawFragment = ""
796 } else {
797 u.RawFragment = f
798 }
799 return nil
800 }
801
802
803
804
805
806
807
808
809
810 func (u *URL) EscapedFragment() string {
811 if u.RawFragment != "" && validEncoded(u.RawFragment, encodeFragment) {
812 f, err := unescape(u.RawFragment, encodeFragment)
813 if err == nil && f == u.Fragment {
814 return u.RawFragment
815 }
816 }
817 return escape(u.Fragment, encodeFragment)
818 }
819
820
821
822 func validOptionalPort(port string) bool {
823 if port == "" {
824 return true
825 }
826 if port[0] != ':' {
827 return false
828 }
829 for _, b := range port[1:] {
830 if b < '0' || b > '9' {
831 return false
832 }
833 }
834 return true
835 }
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858 func (u *URL) String() string {
859 var buf strings.Builder
860
861 n := len(u.Scheme)
862 if u.Opaque != "" {
863 n += len(u.Opaque)
864 } else {
865 if !u.OmitHost && (u.Scheme != "" || u.Host != "" || u.User != nil) {
866 username := u.User.Username()
867 password, _ := u.User.Password()
868 n += len(username) + len(password) + len(u.Host)
869 }
870 n += len(u.Path)
871 }
872 n += len(u.RawQuery) + len(u.RawFragment)
873 n += len(":" + "//" + "//" + ":" + "@" + "/" + "./" + "?" + "#")
874 buf.Grow(n)
875
876 if u.Scheme != "" {
877 buf.WriteString(u.Scheme)
878 buf.WriteByte(':')
879 }
880 if u.Opaque != "" {
881 buf.WriteString(u.Opaque)
882 } else {
883 if u.Scheme != "" || u.Host != "" || u.User != nil {
884 if u.OmitHost && u.Host == "" && u.User == nil {
885
886 } else {
887 if u.Host != "" || u.Path != "" || u.User != nil {
888 buf.WriteString("//")
889 }
890 if ui := u.User; ui != nil {
891 buf.WriteString(ui.String())
892 buf.WriteByte('@')
893 }
894 if h := u.Host; h != "" {
895 buf.WriteString(escape(h, encodeHost))
896 }
897 }
898 }
899 path := u.EscapedPath()
900 if path != "" && path[0] != '/' && u.Host != "" {
901 buf.WriteByte('/')
902 }
903 if buf.Len() == 0 {
904
905
906
907
908
909
910 if segment, _, _ := strings.Cut(path, "/"); strings.Contains(segment, ":") {
911 buf.WriteString("./")
912 }
913 }
914 buf.WriteString(path)
915 }
916 if u.ForceQuery || u.RawQuery != "" {
917 buf.WriteByte('?')
918 buf.WriteString(u.RawQuery)
919 }
920 if u.Fragment != "" {
921 buf.WriteByte('#')
922 buf.WriteString(u.EscapedFragment())
923 }
924 return buf.String()
925 }
926
927
928
929 func (u *URL) Redacted() string {
930 if u == nil {
931 return ""
932 }
933
934 ru := *u
935 if _, has := ru.User.Password(); has {
936 ru.User = UserPassword(ru.User.Username(), "xxxxx")
937 }
938 return ru.String()
939 }
940
941
942
943
944
945 type Values map[string][]string
946
947
948
949
950
951 func (v Values) Get(key string) string {
952 vs := v[key]
953 if len(vs) == 0 {
954 return ""
955 }
956 return vs[0]
957 }
958
959
960
961 func (v Values) Set(key, value string) {
962 v[key] = []string{value}
963 }
964
965
966
967 func (v Values) Add(key, value string) {
968 v[key] = append(v[key], value)
969 }
970
971
972 func (v Values) Del(key string) {
973 delete(v, key)
974 }
975
976
977 func (v Values) Has(key string) bool {
978 _, ok := v[key]
979 return ok
980 }
981
982
983
984
985
986
987
988
989
990
991
992 func ParseQuery(query string) (Values, error) {
993 m := make(Values)
994 err := parseQuery(m, query)
995 return m, err
996 }
997
998 var urlmaxqueryparams = godebug.New("urlmaxqueryparams")
999
1000
1001 const defaultMaxParams = 10000
1002
1003 func urlParamsWithinMax(params int) bool {
1004 withinDefaultMax := params <= defaultMaxParams
1005 if urlmaxqueryparams.Value() == "" {
1006 return withinDefaultMax
1007 }
1008 customMax, err := strconv.Atoi(urlmaxqueryparams.Value())
1009 if err != nil {
1010 return withinDefaultMax
1011 }
1012 withinCustomMax := customMax == 0 || params < customMax
1013 if withinDefaultMax != withinCustomMax {
1014 urlmaxqueryparams.IncNonDefault()
1015 }
1016 return withinCustomMax
1017 }
1018
1019 func parseQuery(m Values, query string) (err error) {
1020 if !urlParamsWithinMax(strings.Count(query, "&") + 1) {
1021 return errors.New("number of URL query parameters exceeded limit")
1022 }
1023 for query != "" {
1024 var key string
1025 key, query, _ = strings.Cut(query, "&")
1026 if strings.Contains(key, ";") {
1027 err = fmt.Errorf("invalid semicolon separator in query")
1028 continue
1029 }
1030 if key == "" {
1031 continue
1032 }
1033 key, value, _ := strings.Cut(key, "=")
1034 key, err1 := QueryUnescape(key)
1035 if err1 != nil {
1036 if err == nil {
1037 err = err1
1038 }
1039 continue
1040 }
1041 value, err1 = QueryUnescape(value)
1042 if err1 != nil {
1043 if err == nil {
1044 err = err1
1045 }
1046 continue
1047 }
1048 m[key] = append(m[key], value)
1049 }
1050 return err
1051 }
1052
1053
1054
1055 func (v Values) Encode() string {
1056 if len(v) == 0 {
1057 return ""
1058 }
1059 var buf strings.Builder
1060 for _, k := range slices.Sorted(maps.Keys(v)) {
1061 vs := v[k]
1062 keyEscaped := QueryEscape(k)
1063 for _, v := range vs {
1064 if buf.Len() > 0 {
1065 buf.WriteByte('&')
1066 }
1067 buf.WriteString(keyEscaped)
1068 buf.WriteByte('=')
1069 buf.WriteString(QueryEscape(v))
1070 }
1071 }
1072 return buf.String()
1073 }
1074
1075
1076
1077 func resolvePath(base, ref string) string {
1078 var full string
1079 if ref == "" {
1080 full = base
1081 } else if ref[0] != '/' {
1082 i := strings.LastIndex(base, "/")
1083 full = base[:i+1] + ref
1084 } else {
1085 full = ref
1086 }
1087 if full == "" {
1088 return ""
1089 }
1090
1091 var (
1092 elem string
1093 dst strings.Builder
1094 )
1095 first := true
1096 remaining := full
1097
1098 dst.WriteByte('/')
1099 found := true
1100 for found {
1101 elem, remaining, found = strings.Cut(remaining, "/")
1102 if elem == "." {
1103 first = false
1104
1105 continue
1106 }
1107
1108 if elem == ".." {
1109
1110 str := dst.String()[1:]
1111 index := strings.LastIndexByte(str, '/')
1112
1113 dst.Reset()
1114 dst.WriteByte('/')
1115 if index == -1 {
1116 first = true
1117 } else {
1118 dst.WriteString(str[:index])
1119 }
1120 } else {
1121 if !first {
1122 dst.WriteByte('/')
1123 }
1124 dst.WriteString(elem)
1125 first = false
1126 }
1127 }
1128
1129 if elem == "." || elem == ".." {
1130 dst.WriteByte('/')
1131 }
1132
1133
1134 r := dst.String()
1135 if len(r) > 1 && r[1] == '/' {
1136 r = r[1:]
1137 }
1138 return r
1139 }
1140
1141
1142
1143 func (u *URL) IsAbs() bool {
1144 return u.Scheme != ""
1145 }
1146
1147
1148
1149
1150 func (u *URL) Parse(ref string) (*URL, error) {
1151 refURL, err := Parse(ref)
1152 if err != nil {
1153 return nil, err
1154 }
1155 return u.ResolveReference(refURL), nil
1156 }
1157
1158
1159
1160
1161
1162
1163
1164 func (u *URL) ResolveReference(ref *URL) *URL {
1165 url := *ref
1166 if ref.Scheme == "" {
1167 url.Scheme = u.Scheme
1168 }
1169 if ref.Scheme != "" || ref.Host != "" || ref.User != nil {
1170
1171
1172
1173 url.setPath(resolvePath(ref.EscapedPath(), ""))
1174 return &url
1175 }
1176 if ref.Opaque != "" {
1177 url.User = nil
1178 url.Host = ""
1179 url.Path = ""
1180 return &url
1181 }
1182 if ref.Path == "" && !ref.ForceQuery && ref.RawQuery == "" {
1183 url.RawQuery = u.RawQuery
1184 if ref.Fragment == "" {
1185 url.Fragment = u.Fragment
1186 url.RawFragment = u.RawFragment
1187 }
1188 }
1189 if ref.Path == "" && u.Opaque != "" {
1190 url.Opaque = u.Opaque
1191 url.User = nil
1192 url.Host = ""
1193 url.Path = ""
1194 return &url
1195 }
1196
1197 url.Host = u.Host
1198 url.User = u.User
1199 url.setPath(resolvePath(u.EscapedPath(), ref.EscapedPath()))
1200 return &url
1201 }
1202
1203
1204
1205
1206 func (u *URL) Query() Values {
1207 v, _ := ParseQuery(u.RawQuery)
1208 return v
1209 }
1210
1211
1212
1213 func (u *URL) RequestURI() string {
1214 result := u.Opaque
1215 if result == "" {
1216 result = u.EscapedPath()
1217 if result == "" {
1218 result = "/"
1219 }
1220 } else {
1221 if strings.HasPrefix(result, "//") {
1222 result = u.Scheme + ":" + result
1223 }
1224 }
1225 if u.ForceQuery || u.RawQuery != "" {
1226 result += "?" + u.RawQuery
1227 }
1228 return result
1229 }
1230
1231
1232
1233
1234
1235 func (u *URL) Hostname() string {
1236 host, _ := splitHostPort(u.Host)
1237 return host
1238 }
1239
1240
1241
1242
1243 func (u *URL) Port() string {
1244 _, port := splitHostPort(u.Host)
1245 return port
1246 }
1247
1248
1249
1250
1251 func splitHostPort(hostPort string) (host, port string) {
1252 host = hostPort
1253
1254 colon := strings.LastIndexByte(host, ':')
1255 if colon != -1 && validOptionalPort(host[colon:]) {
1256 host, port = host[:colon], host[colon+1:]
1257 }
1258
1259 if strings.HasPrefix(host, "[") && strings.HasSuffix(host, "]") {
1260 host = host[1 : len(host)-1]
1261 }
1262
1263 return
1264 }
1265
1266
1267
1268
1269 func (u *URL) MarshalBinary() (text []byte, err error) {
1270 return u.AppendBinary(nil)
1271 }
1272
1273 func (u *URL) AppendBinary(b []byte) ([]byte, error) {
1274 return append(b, u.String()...), nil
1275 }
1276
1277 func (u *URL) UnmarshalBinary(text []byte) error {
1278 u1, err := Parse(string(text))
1279 if err != nil {
1280 return err
1281 }
1282 *u = *u1
1283 return nil
1284 }
1285
1286
1287
1288
1289 func (u *URL) JoinPath(elem ...string) *URL {
1290 elem = append([]string{u.EscapedPath()}, elem...)
1291 var p string
1292 if !strings.HasPrefix(elem[0], "/") {
1293
1294
1295 elem[0] = "/" + elem[0]
1296 p = path.Join(elem...)[1:]
1297 } else {
1298 p = path.Join(elem...)
1299 }
1300
1301
1302 if strings.HasSuffix(elem[len(elem)-1], "/") && !strings.HasSuffix(p, "/") {
1303 p += "/"
1304 }
1305 url := *u
1306 url.setPath(p)
1307 return &url
1308 }
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319 func validUserinfo(s string) bool {
1320 for _, r := range s {
1321 if 'A' <= r && r <= 'Z' {
1322 continue
1323 }
1324 if 'a' <= r && r <= 'z' {
1325 continue
1326 }
1327 if '0' <= r && r <= '9' {
1328 continue
1329 }
1330 switch r {
1331 case '-', '.', '_', ':', '~', '!', '$', '&', '\'',
1332 '(', ')', '*', '+', ',', ';', '=', '%':
1333 continue
1334 case '@':
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344 continue
1345 default:
1346 return false
1347 }
1348 }
1349 return true
1350 }
1351
1352
1353 func stringContainsCTLByte(s string) bool {
1354 for i := 0; i < len(s); i++ {
1355 b := s[i]
1356 if b < ' ' || b == 0x7f {
1357 return true
1358 }
1359 }
1360 return false
1361 }
1362
1363
1364
1365 func JoinPath(base string, elem ...string) (result string, err error) {
1366 url, err := Parse(base)
1367 if err != nil {
1368 return
1369 }
1370 result = url.JoinPath(elem...).String()
1371 return
1372 }
1373
View as plain text