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 func MustParse(rawURL string) *URL {
418 url, err := Parse(rawURL)
419 if err != nil {
420 panic(err)
421 }
422 return url
423 }
424
425
426
427
428
429
430 func ParseRequestURI(rawURL string) (*URL, error) {
431 url, err := parse(rawURL, true)
432 if err != nil {
433 return nil, &Error{"parse", rawURL, err}
434 }
435 return url, nil
436 }
437
438
439
440
441
442 func parse(rawURL string, viaRequest bool) (*URL, error) {
443 var rest string
444 var err error
445
446 if stringContainsCTLByte(rawURL) {
447 return nil, errors.New("net/url: invalid control character in URL")
448 }
449
450 if rawURL == "" && viaRequest {
451 return nil, errors.New("empty url")
452 }
453 url := new(URL)
454
455 if rawURL == "*" {
456 url.Path = "*"
457 return url, nil
458 }
459
460
461
462 if url.Scheme, rest, err = getScheme(rawURL); err != nil {
463 return nil, err
464 }
465 url.Scheme = strings.ToLower(url.Scheme)
466
467 if strings.HasSuffix(rest, "?") && strings.Count(rest, "?") == 1 {
468 url.ForceQuery = true
469 rest = rest[:len(rest)-1]
470 } else {
471 rest, url.RawQuery, _ = strings.Cut(rest, "?")
472 }
473
474 if !strings.HasPrefix(rest, "/") {
475 if url.Scheme != "" {
476
477 url.Opaque = rest
478 return url, nil
479 }
480 if viaRequest {
481 return nil, errors.New("invalid URI for request")
482 }
483
484
485
486
487
488
489
490 if segment, _, _ := strings.Cut(rest, "/"); strings.Contains(segment, ":") {
491
492 return nil, errors.New("first path segment in URL cannot contain colon")
493 }
494 }
495
496 if (url.Scheme != "" || !viaRequest && !strings.HasPrefix(rest, "///")) && strings.HasPrefix(rest, "//") {
497 var authority string
498 authority, rest = rest[2:], ""
499 if i := strings.Index(authority, "/"); i >= 0 {
500 authority, rest = authority[:i], authority[i:]
501 }
502 url.User, url.Host, err = parseAuthority(url.Scheme, authority)
503 if err != nil {
504 return nil, err
505 }
506 } else if url.Scheme != "" && strings.HasPrefix(rest, "/") {
507
508
509 url.OmitHost = true
510 }
511
512
513
514
515
516 if err := url.setPath(rest); err != nil {
517 return nil, err
518 }
519 return url, nil
520 }
521
522 func parseAuthority(scheme, authority string) (user *Userinfo, host string, err error) {
523 i := strings.LastIndex(authority, "@")
524 if i < 0 {
525 host, err = parseHost(scheme, authority)
526 } else {
527 host, err = parseHost(scheme, authority[i+1:])
528 }
529 if err != nil {
530 return nil, "", err
531 }
532 if i < 0 {
533 return nil, host, nil
534 }
535 userinfo := authority[:i]
536 if !validUserinfo(userinfo) {
537 return nil, "", errors.New("net/url: invalid userinfo")
538 }
539 if !strings.Contains(userinfo, ":") {
540 if userinfo, err = unescape(userinfo, encodeUserPassword); err != nil {
541 return nil, "", err
542 }
543 user = User(userinfo)
544 } else {
545 username, password, _ := strings.Cut(userinfo, ":")
546 if username, err = unescape(username, encodeUserPassword); err != nil {
547 return nil, "", err
548 }
549 if password, err = unescape(password, encodeUserPassword); err != nil {
550 return nil, "", err
551 }
552 user = UserPassword(username, password)
553 }
554 return user, host, nil
555 }
556
557
558
559 func parseHost(scheme, host string) (string, error) {
560 if openBracketIdx := strings.LastIndex(host, "["); openBracketIdx > 0 {
561 return "", errors.New("invalid IP-literal")
562 } else if openBracketIdx == 0 {
563
564
565 closeBracketIdx := strings.LastIndex(host, "]")
566 if closeBracketIdx < 0 {
567 return "", errors.New("missing ']' in host")
568 }
569
570 colonPort := host[closeBracketIdx+1:]
571 if !validOptionalPort(colonPort) {
572 return "", fmt.Errorf("invalid port %q after host", colonPort)
573 }
574 unescapedColonPort, err := unescape(colonPort, encodeHost)
575 if err != nil {
576 return "", err
577 }
578
579 hostname := host[openBracketIdx+1 : closeBracketIdx]
580 var unescapedHostname string
581
582
583
584
585
586
587 zoneIdx := strings.Index(hostname, "%25")
588 if zoneIdx >= 0 {
589 hostPart, err := unescape(hostname[:zoneIdx], encodeHost)
590 if err != nil {
591 return "", err
592 }
593 zonePart, err := unescape(hostname[zoneIdx:], encodeZone)
594 if err != nil {
595 return "", err
596 }
597 unescapedHostname = hostPart + zonePart
598 } else {
599 var err error
600 unescapedHostname, err = unescape(hostname, encodeHost)
601 if err != nil {
602 return "", err
603 }
604 }
605
606
607
608
609 addr, err := netip.ParseAddr(unescapedHostname)
610 if err != nil {
611 return "", fmt.Errorf("invalid host: %w", err)
612 }
613 if addr.Is4() {
614 return "", errors.New("invalid IP-literal")
615 }
616 return "[" + unescapedHostname + "]" + unescapedColonPort, nil
617 } else if i := strings.Index(host, ":"); i != -1 {
618 lastColon := strings.LastIndex(host, ":")
619 if lastColon != i {
620
621
622
623
624
625
626
627
628
629
630 if scheme == "http" || scheme == "https" {
631 if urlstrictcolons.Value() == "0" {
632 urlstrictcolons.IncNonDefault()
633 i = lastColon
634 }
635 } else {
636 i = lastColon
637 }
638 }
639 colonPort := host[i:]
640 if !validOptionalPort(colonPort) {
641 return "", fmt.Errorf("invalid port %q after host", colonPort)
642 }
643 }
644
645 var err error
646 if host, err = unescape(host, encodeHost); err != nil {
647 return "", err
648 }
649 return host, nil
650 }
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670 func (u *URL) setPath(p string) error {
671 path, err := unescape(p, encodePath)
672 if err != nil {
673 return err
674 }
675 u.Path = path
676 if escp := escape(path, encodePath); p == escp {
677
678 u.RawPath = ""
679 } else {
680 u.RawPath = p
681 }
682 return nil
683 }
684
685
686 func badSetPath(*URL, string) error
687
688
689
690
691
692
693
694
695
696
697 func (u *URL) EscapedPath() string {
698 if u.RawPath != "" && validEncoded(u.RawPath, encodePath) {
699 p, err := unescape(u.RawPath, encodePath)
700 if err == nil && p == u.Path {
701 return u.RawPath
702 }
703 }
704 if u.Path == "*" {
705 return "*"
706 }
707 return escape(u.Path, encodePath)
708 }
709
710
711
712
713 func validEncoded(s string, mode encoding) bool {
714 for i := 0; i < len(s); i++ {
715
716
717
718
719
720 switch s[i] {
721 case '!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=', ':', '@':
722
723 case '[', ']':
724
725 case '%':
726
727 default:
728 if shouldEscape(s[i], mode) {
729 return false
730 }
731 }
732 }
733 return true
734 }
735
736
737 func (u *URL) setFragment(f string) error {
738 frag, err := unescape(f, encodeFragment)
739 if err != nil {
740 return err
741 }
742 u.Fragment = frag
743 if escf := escape(frag, encodeFragment); f == escf {
744
745 u.RawFragment = ""
746 } else {
747 u.RawFragment = f
748 }
749 return nil
750 }
751
752
753
754
755
756
757
758
759
760 func (u *URL) EscapedFragment() string {
761 if u.RawFragment != "" && validEncoded(u.RawFragment, encodeFragment) {
762 f, err := unescape(u.RawFragment, encodeFragment)
763 if err == nil && f == u.Fragment {
764 return u.RawFragment
765 }
766 }
767 return escape(u.Fragment, encodeFragment)
768 }
769
770
771
772 func validOptionalPort(port string) bool {
773 if port == "" {
774 return true
775 }
776 if port[0] != ':' {
777 return false
778 }
779 for _, b := range port[1:] {
780 if b < '0' || b > '9' {
781 return false
782 }
783 }
784 return true
785 }
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808 func (u *URL) String() string {
809 var buf strings.Builder
810
811 n := len(u.Scheme)
812 if u.Opaque != "" {
813 n += len(u.Opaque)
814 } else {
815 if !u.OmitHost && (u.Scheme != "" || u.Host != "" || u.User != nil) {
816 username := u.User.Username()
817 password, _ := u.User.Password()
818 n += len(username) + len(password) + len(u.Host)
819 }
820 n += len(u.Path)
821 }
822 n += len(u.RawQuery) + len(u.RawFragment)
823 n += len(":" + "//" + "//" + ":" + "@" + "/" + "./" + "?" + "#")
824 buf.Grow(n)
825
826 if u.Scheme != "" {
827 buf.WriteString(u.Scheme)
828 buf.WriteByte(':')
829 }
830 if u.Opaque != "" {
831 buf.WriteString(u.Opaque)
832 } else {
833 if u.Scheme != "" || u.Host != "" || u.User != nil {
834 if u.OmitHost && u.Host == "" && u.User == nil {
835
836 } else {
837 if u.Host != "" || u.Path != "" || u.User != nil {
838 buf.WriteString("//")
839 }
840 if ui := u.User; ui != nil {
841 buf.WriteString(ui.String())
842 buf.WriteByte('@')
843 }
844 if h := u.Host; h != "" {
845 buf.WriteString(escape(h, encodeHost))
846 }
847 }
848 }
849 path := u.EscapedPath()
850 if u.OmitHost && u.Host == "" && u.User == nil && strings.HasPrefix(path, "//") {
851
852
853
854 buf.WriteString("%2F")
855 path = path[1:]
856 }
857 if path != "" && path[0] != '/' && u.Host != "" {
858 buf.WriteByte('/')
859 }
860 if buf.Len() == 0 {
861
862
863
864
865
866
867 if segment, _, _ := strings.Cut(path, "/"); strings.Contains(segment, ":") {
868 buf.WriteString("./")
869 }
870 }
871 buf.WriteString(path)
872 }
873 if u.ForceQuery || u.RawQuery != "" {
874 buf.WriteByte('?')
875 buf.WriteString(u.RawQuery)
876 }
877 if u.Fragment != "" {
878 buf.WriteByte('#')
879 buf.WriteString(u.EscapedFragment())
880 }
881 return buf.String()
882 }
883
884
885
886 func (u *URL) Redacted() string {
887 if u == nil {
888 return ""
889 }
890
891 ru := *u
892 if _, has := ru.User.Password(); has {
893 ru.User = UserPassword(ru.User.Username(), "xxxxx")
894 }
895 return ru.String()
896 }
897
898
899
900
901
902 type Values map[string][]string
903
904
905
906
907
908 func (v Values) Get(key string) string {
909 vs := v[key]
910 if len(vs) == 0 {
911 return ""
912 }
913 return vs[0]
914 }
915
916
917
918 func (v Values) Set(key, value string) {
919 v[key] = []string{value}
920 }
921
922
923
924 func (v Values) Add(key, value string) {
925 v[key] = append(v[key], value)
926 }
927
928
929 func (v Values) Del(key string) {
930 delete(v, key)
931 }
932
933
934 func (v Values) Has(key string) bool {
935 _, ok := v[key]
936 return ok
937 }
938
939
940 func (vs Values) Clone() Values {
941 if vs == nil {
942 return nil
943 }
944
945 newVals := make(Values, len(vs))
946 for k, v := range vs {
947 newVals[k] = slices.Clone(v)
948 }
949 return newVals
950 }
951
952
953
954
955
956
957
958
959
960
961
962 func ParseQuery(query string) (Values, error) {
963 m := make(Values)
964 err := parseQuery(m, query)
965 return m, err
966 }
967
968 var urlmaxqueryparams = godebug.New("urlmaxqueryparams")
969
970
971 const defaultMaxParams = 10000
972
973 func urlParamsWithinMax(params int) bool {
974 withinDefaultMax := params <= defaultMaxParams
975 if urlmaxqueryparams.Value() == "" {
976 return withinDefaultMax
977 }
978 customMax, err := strconv.Atoi(urlmaxqueryparams.Value())
979 if err != nil {
980 return withinDefaultMax
981 }
982 withinCustomMax := customMax == 0 || params < customMax
983 if withinDefaultMax != withinCustomMax {
984 urlmaxqueryparams.IncNonDefault()
985 }
986 return withinCustomMax
987 }
988
989 func parseQuery(m Values, query string) (err error) {
990 if !urlParamsWithinMax(strings.Count(query, "&") + 1) {
991 return errors.New("number of URL query parameters exceeded limit")
992 }
993 for query != "" {
994 var key string
995 key, query, _ = strings.Cut(query, "&")
996 if strings.Contains(key, ";") {
997 err = fmt.Errorf("invalid semicolon separator in query")
998 continue
999 }
1000 if key == "" {
1001 continue
1002 }
1003 key, value, _ := strings.Cut(key, "=")
1004 key, err1 := QueryUnescape(key)
1005 if err1 != nil {
1006 if err == nil {
1007 err = err1
1008 }
1009 continue
1010 }
1011 value, err1 = QueryUnescape(value)
1012 if err1 != nil {
1013 if err == nil {
1014 err = err1
1015 }
1016 continue
1017 }
1018 m[key] = append(m[key], value)
1019 }
1020 return err
1021 }
1022
1023
1024
1025 func (v Values) Encode() string {
1026 if len(v) == 0 {
1027 return ""
1028 }
1029 var buf strings.Builder
1030
1031
1032 keys := make([]string, len(v))
1033 var i int
1034 for k := range v {
1035 keys[i] = k
1036 i++
1037 }
1038 slices.Sort(keys)
1039 for _, k := range keys {
1040 vs := v[k]
1041 keyEscaped := QueryEscape(k)
1042 for _, v := range vs {
1043 if buf.Len() > 0 {
1044 buf.WriteByte('&')
1045 }
1046 buf.WriteString(keyEscaped)
1047 buf.WriteByte('=')
1048 buf.WriteString(QueryEscape(v))
1049 }
1050 }
1051 return buf.String()
1052 }
1053
1054
1055
1056 func resolvePath(base, ref string) string {
1057 var full string
1058 if ref == "" {
1059 full = base
1060 } else if ref[0] != '/' {
1061 i := strings.LastIndex(base, "/")
1062 full = base[:i+1] + ref
1063 } else {
1064 full = ref
1065 }
1066 if full == "" {
1067 return ""
1068 }
1069
1070 dst := make([]byte, 0, len(full)+1)
1071 dst = append(dst, '/')
1072 elem := ""
1073 remaining := full
1074 found := true
1075 first := true
1076 for found {
1077 elem, remaining, found = strings.Cut(remaining, "/")
1078 switch elem {
1079 case ".":
1080 first = false
1081 continue
1082 case "..":
1083 if i := bytes.LastIndexByte(dst[1:], '/'); i >= 0 {
1084 dst = dst[:i+1]
1085 } else {
1086 dst = dst[:1]
1087 }
1088 first = len(dst) == 1
1089 default:
1090 if !first {
1091 dst = append(dst, '/')
1092 }
1093 dst = append(dst, elem...)
1094 first = false
1095 }
1096 }
1097
1098 if elem == "." || elem == ".." {
1099 dst = append(dst, '/')
1100 }
1101
1102
1103 if len(dst) > 1 && dst[1] == '/' {
1104 return string(dst[1:])
1105 }
1106 return string(dst)
1107 }
1108
1109
1110
1111 func (u *URL) IsAbs() bool {
1112 return u.Scheme != ""
1113 }
1114
1115
1116
1117
1118 func (u *URL) Parse(ref string) (*URL, error) {
1119 refURL, err := Parse(ref)
1120 if err != nil {
1121 return nil, err
1122 }
1123 return u.ResolveReference(refURL), nil
1124 }
1125
1126
1127
1128
1129
1130
1131
1132 func (u *URL) ResolveReference(ref *URL) *URL {
1133 url := *ref
1134 if ref.Scheme == "" {
1135 url.Scheme = u.Scheme
1136 }
1137 if ref.Scheme != "" || ref.Host != "" || ref.User != nil {
1138
1139
1140
1141 url.setPath(resolvePath(ref.EscapedPath(), ""))
1142 return &url
1143 }
1144 if ref.Opaque != "" {
1145 url.User = nil
1146 url.Host = ""
1147 url.Path = ""
1148 return &url
1149 }
1150 if ref.Path == "" && !ref.ForceQuery && ref.RawQuery == "" {
1151 url.RawQuery = u.RawQuery
1152 if ref.Fragment == "" {
1153 url.Fragment = u.Fragment
1154 url.RawFragment = u.RawFragment
1155 }
1156 }
1157 if ref.Path == "" && u.Opaque != "" {
1158 url.Opaque = u.Opaque
1159 url.User = nil
1160 url.Host = ""
1161 url.Path = ""
1162 return &url
1163 }
1164
1165 url.Host = u.Host
1166 url.User = u.User
1167 url.setPath(resolvePath(u.EscapedPath(), ref.EscapedPath()))
1168 return &url
1169 }
1170
1171
1172
1173
1174 func (u *URL) Query() Values {
1175 v, _ := ParseQuery(u.RawQuery)
1176 return v
1177 }
1178
1179
1180
1181 func (u *URL) RequestURI() string {
1182 result := u.Opaque
1183 if result == "" {
1184 result = u.EscapedPath()
1185 if result == "" {
1186 result = "/"
1187 }
1188 } else {
1189 if strings.HasPrefix(result, "//") {
1190 result = u.Scheme + ":" + result
1191 }
1192 }
1193 if u.ForceQuery || u.RawQuery != "" {
1194 result += "?" + u.RawQuery
1195 }
1196 return result
1197 }
1198
1199
1200
1201
1202
1203 func (u *URL) Hostname() string {
1204 host, _ := splitHostPort(u.Host)
1205 return host
1206 }
1207
1208
1209
1210
1211 func (u *URL) Port() string {
1212 _, port := splitHostPort(u.Host)
1213 return port
1214 }
1215
1216
1217
1218
1219 func splitHostPort(hostPort string) (host, port string) {
1220 host = hostPort
1221
1222 colon := strings.LastIndexByte(host, ':')
1223 if colon != -1 && validOptionalPort(host[colon:]) {
1224 host, port = host[:colon], host[colon+1:]
1225 }
1226
1227 if strings.HasPrefix(host, "[") && strings.HasSuffix(host, "]") {
1228 host = host[1 : len(host)-1]
1229 }
1230
1231 return
1232 }
1233
1234
1235
1236
1237 func (u *URL) MarshalBinary() (text []byte, err error) {
1238 return u.AppendBinary(nil)
1239 }
1240
1241 func (u *URL) AppendBinary(b []byte) ([]byte, error) {
1242 return append(b, u.String()...), nil
1243 }
1244
1245 func (u *URL) UnmarshalBinary(text []byte) error {
1246 u1, err := Parse(string(text))
1247 if err != nil {
1248 return err
1249 }
1250 *u = *u1
1251 return nil
1252 }
1253
1254
1255
1256
1257
1258 func (u *URL) JoinPath(elem ...string) *URL {
1259 url, _ := u.joinPath(elem...)
1260 return url
1261 }
1262
1263 func (u *URL) joinPath(elem ...string) (*URL, error) {
1264 elem = append([]string{u.EscapedPath()}, elem...)
1265 var p string
1266 if !strings.HasPrefix(elem[0], "/") {
1267
1268
1269 elem[0] = "/" + elem[0]
1270 p = path.Join(elem...)[1:]
1271 } else {
1272 p = path.Join(elem...)
1273 }
1274
1275
1276 if strings.HasSuffix(elem[len(elem)-1], "/") && !strings.HasSuffix(p, "/") {
1277 p += "/"
1278 }
1279 url := *u
1280 err := url.setPath(p)
1281 return &url, err
1282 }
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293 func validUserinfo(s string) bool {
1294 for _, r := range s {
1295 if 'A' <= r && r <= 'Z' {
1296 continue
1297 }
1298 if 'a' <= r && r <= 'z' {
1299 continue
1300 }
1301 if '0' <= r && r <= '9' {
1302 continue
1303 }
1304 switch r {
1305 case '-', '.', '_', ':', '~', '!', '$', '&', '\'',
1306 '(', ')', '*', '+', ',', ';', '=', '%':
1307 continue
1308 case '@':
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318 continue
1319 default:
1320 return false
1321 }
1322 }
1323 return true
1324 }
1325
1326
1327 func stringContainsCTLByte(s string) bool {
1328 for i := 0; i < len(s); i++ {
1329 b := s[i]
1330 if b < ' ' || b == 0x7f {
1331 return true
1332 }
1333 }
1334 return false
1335 }
1336
1337
1338
1339
1340 func JoinPath(base string, elem ...string) (result string, err error) {
1341 url, err := Parse(base)
1342 if err != nil {
1343 return
1344 }
1345 res, err := url.joinPath(elem...)
1346 if err != nil {
1347 return "", err
1348 }
1349 return res.String(), nil
1350 }
1351
1352
1353 func (u *URL) Clone() *URL {
1354 if u == nil {
1355 return nil
1356 }
1357
1358 uc := new(*u)
1359 if u.User != nil {
1360 uc.User = new(*u.User)
1361 }
1362 return uc
1363 }
1364
View as plain text