@@ -356,15 +356,16 @@ func escape(s string, mode encoding) string {
356
356
// URL's String method uses the EscapedPath method to obtain the path. See the
357
357
// EscapedPath method for more details.
358
358
type URL struct {
359
- Scheme string
360
- Opaque string // encoded opaque data
361
- User * Userinfo // username and password information
362
- Host string // host or host:port
363
- Path string // path (relative paths may omit leading slash)
364
- RawPath string // encoded path hint (see EscapedPath method)
365
- ForceQuery bool // append a query ('?') even if RawQuery is empty
366
- RawQuery string // encoded query values, without '?'
367
- Fragment string // fragment for references, without '#'
359
+ Scheme string
360
+ Opaque string // encoded opaque data
361
+ User * Userinfo // username and password information
362
+ Host string // host or host:port
363
+ Path string // path (relative paths may omit leading slash)
364
+ RawPath string // encoded path hint (see EscapedPath method)
365
+ ForceQuery bool // append a query ('?') even if RawQuery is empty
366
+ RawQuery string // encoded query values, without '?'
367
+ Fragment string // fragment for references, without '#'
368
+ RawFragment string // encoded fragment hint (see EscapedFragment method)
368
369
}
369
370
370
371
// User returns a Userinfo containing the provided username
@@ -481,7 +482,7 @@ func Parse(rawurl string) (*URL, error) {
481
482
if frag == "" {
482
483
return url , nil
483
484
}
484
- if url . Fragment , err = unescape (frag , encodeFragment ); err != nil {
485
+ if err = url . setFragment (frag ); err != nil {
485
486
return nil , & Error {"parse" , rawurl , err }
486
487
}
487
488
return url , nil
@@ -697,7 +698,7 @@ func (u *URL) setPath(p string) error {
697
698
// In general, code should call EscapedPath instead of
698
699
// reading u.RawPath directly.
699
700
func (u * URL ) EscapedPath () string {
700
- if u .RawPath != "" && validEncodedPath (u .RawPath ) {
701
+ if u .RawPath != "" && validEncoded (u .RawPath , encodePath ) {
701
702
p , err := unescape (u .RawPath , encodePath )
702
703
if err == nil && p == u .Path {
703
704
return u .RawPath
@@ -709,9 +710,10 @@ func (u *URL) EscapedPath() string {
709
710
return escape (u .Path , encodePath )
710
711
}
711
712
712
- // validEncodedPath reports whether s is a valid encoded path.
713
- // It must not contain any bytes that require escaping during path encoding.
714
- func validEncodedPath (s string ) bool {
713
+ // validEncoded reports whether s is a valid encoded path or fragment,
714
+ // according to mode.
715
+ // It must not contain any bytes that require escaping during encoding.
716
+ func validEncoded (s string , mode encoding ) bool {
715
717
for i := 0 ; i < len (s ); i ++ {
716
718
// RFC 3986, Appendix A.
717
719
// pchar = unreserved / pct-encoded / sub-delims / ":" / "@".
@@ -726,14 +728,48 @@ func validEncodedPath(s string) bool {
726
728
case '%' :
727
729
// ok - percent encoded, will decode
728
730
default :
729
- if shouldEscape (s [i ], encodePath ) {
731
+ if shouldEscape (s [i ], mode ) {
730
732
return false
731
733
}
732
734
}
733
735
}
734
736
return true
735
737
}
736
738
739
+ // setFragment is like setPath but for Fragment/RawFragment.
740
+ func (u * URL ) setFragment (f string ) error {
741
+ frag , err := unescape (f , encodeFragment )
742
+ if err != nil {
743
+ return err
744
+ }
745
+ u .Fragment = frag
746
+ if escf := escape (frag , encodeFragment ); f == escf {
747
+ // Default encoding is fine.
748
+ u .RawFragment = ""
749
+ } else {
750
+ u .RawFragment = f
751
+ }
752
+ return nil
753
+ }
754
+
755
+ // EscapedFragment returns the escaped form of u.Fragment.
756
+ // In general there are multiple possible escaped forms of any fragment.
757
+ // EscapedFragment returns u.RawFragment when it is a valid escaping of u.Fragment.
758
+ // Otherwise EscapedFragment ignores u.RawFragment and computes an escaped
759
+ // form on its own.
760
+ // The String method uses EscapedFragment to construct its result.
761
+ // In general, code should call EscapedFragment instead of
762
+ // reading u.RawFragment directly.
763
+ func (u * URL ) EscapedFragment () string {
764
+ if u .RawFragment != "" && validEncoded (u .RawFragment , encodeFragment ) {
765
+ f , err := unescape (u .RawFragment , encodeFragment )
766
+ if err == nil && f == u .Fragment {
767
+ return u .RawFragment
768
+ }
769
+ }
770
+ return escape (u .Fragment , encodeFragment )
771
+ }
772
+
737
773
// validOptionalPort reports whether port is either an empty string
738
774
// or matches /^:\d*$/
739
775
func validOptionalPort (port string ) bool {
@@ -816,7 +852,7 @@ func (u *URL) String() string {
816
852
}
817
853
if u .Fragment != "" {
818
854
buf .WriteByte ('#' )
819
- buf .WriteString (escape ( u . Fragment , encodeFragment ))
855
+ buf .WriteString (u . EscapedFragment ( ))
820
856
}
821
857
return buf .String ()
822
858
}
@@ -1030,6 +1066,7 @@ func (u *URL) ResolveReference(ref *URL) *URL {
1030
1066
url .RawQuery = u .RawQuery
1031
1067
if ref .Fragment == "" {
1032
1068
url .Fragment = u .Fragment
1069
+ url .RawFragment = u .RawFragment
1033
1070
}
1034
1071
}
1035
1072
// The "abs_path" or "rel_path" cases.
0 commit comments