if module.CanonicalVersion(m.Version) != m.Version {
return "", fmt.Errorf("non-canonical module version %q", m.Version)
}
- return filepath.Join(dir, m.Version+"."+suffix), nil
+ encVer, err := module.EncodeVersion(m.Version)
+ if err != nil {
+ return "", err
+ }
+ return filepath.Join(dir, encVer+"."+suffix), nil
}
func DownloadDir(m module.Version) (string, error) {
if module.CanonicalVersion(m.Version) != m.Version {
return "", fmt.Errorf("non-canonical module version %q", m.Version)
}
- return filepath.Join(SrcMod, enc+"@"+m.Version), nil
+ encVer, err := module.EncodeVersion(m.Version)
+ if err != nil {
+ return "", err
+ }
+ return filepath.Join(SrcMod, enc+"@"+encVer), nil
}
// A cachingRepo is a cache around an underlying Repo,
func (p *proxyRepo) Stat(rev string) (*RevInfo, error) {
var data []byte
- err := webGetBytes(p.url+"/@v/"+pathEscape(rev)+".info", &data)
+ encRev, err := module.EncodeVersion(rev)
+ if err != nil {
+ return nil, err
+ }
+ err = webGetBytes(p.url+"/@v/"+pathEscape(encRev)+".info", &data)
if err != nil {
return nil, err
}
func (p *proxyRepo) GoMod(version string) ([]byte, error) {
var data []byte
- err := webGetBytes(p.url+"/@v/"+pathEscape(version)+".mod", &data)
+ encVer, err := module.EncodeVersion(version)
+ if err != nil {
+ return nil, err
+ }
+ err = webGetBytes(p.url+"/@v/"+pathEscape(encVer)+".mod", &data)
if err != nil {
return nil, err
}
func (p *proxyRepo) Zip(version string, tmpdir string) (tmpfile string, err error) {
var body io.ReadCloser
- err = webGetBody(p.url+"/@v/"+pathEscape(version)+".zip", &body)
+ encVer, err := module.EncodeVersion(version)
+ if err != nil {
+ return "", err
+ }
+ err = webGetBody(p.url+"/@v/"+pathEscape(encVer)+".zip", &body)
if err != nil {
return "", err
}
return "", err
}
+ return encodeString(path)
+}
+
+// EncodeVersion returns the safe encoding of the given module version.
+// Versions are allowed to be in non-semver form but must be valid file names
+// and not contain exclamation marks.
+func EncodeVersion(v string) (encoding string, err error) {
+ if err := checkElem(v, true); err != nil || strings.Contains(v, "!") {
+ return "", fmt.Errorf("disallowed version string %q", v)
+ }
+ return encodeString(v)
+}
+
+func encodeString(s string) (encoding string, err error) {
haveUpper := false
- for _, r := range path {
+ for _, r := range s {
if r == '!' || r >= utf8.RuneSelf {
// This should be disallowed by CheckPath, but diagnose anyway.
// The correctness of the encoding loop below depends on it.
}
if !haveUpper {
- return path, nil
+ return s, nil
}
var buf []byte
- for _, r := range path {
+ for _, r := range s {
if 'A' <= r && r <= 'Z' {
buf = append(buf, '!', byte(r+'a'-'A'))
} else {
}
// DecodePath returns the module path of the given safe encoding.
-// It fails if the encoding is invalid.
+// It fails if the encoding is invalid or encodes an invalid path.
func DecodePath(encoding string) (path string, err error) {
+ path, ok := decodeString(encoding)
+ if !ok {
+ return "", fmt.Errorf("invalid module path encoding %q", encoding)
+ }
+ if err := CheckPath(path); err != nil {
+ return "", fmt.Errorf("invalid module path encoding %q: %v", encoding, err)
+ }
+ return path, nil
+}
+
+// DecodeVersion returns the version string for the given safe encoding.
+// It fails if the encoding is invalid or encodes an invalid version.
+// Versions are allowed to be in non-semver form but must be valid file names
+// and not contain exclamation marks.
+func DecodeVersion(encoding string) (v string, err error) {
+ v, ok := decodeString(encoding)
+ if !ok {
+ return "", fmt.Errorf("invalid version encoding %q", encoding)
+ }
+ if err := checkElem(v, true); err != nil {
+ return "", fmt.Errorf("disallowed version string %q", v)
+ }
+ return v, nil
+}
+
+func decodeString(encoding string) (string, bool) {
var buf []byte
bang := false
for _, r := range encoding {
if r >= utf8.RuneSelf {
- goto BadEncoding
+ return "", false
}
if bang {
bang = false
if r < 'a' || 'z' < r {
- goto BadEncoding
+ return "", false
}
buf = append(buf, byte(r+'A'-'a'))
continue
continue
}
if 'A' <= r && r <= 'Z' {
- goto BadEncoding
+ return "", false
}
buf = append(buf, byte(r))
}
if bang {
- goto BadEncoding
+ return "", false
}
- path = string(buf)
- if err := CheckPath(path); err != nil {
- return "", fmt.Errorf("invalid module path encoding %q: %v", encoding, err)
- }
- return path, nil
-
-BadEncoding:
- return "", fmt.Errorf("invalid module path encoding %q", encoding)
+ return string(buf), true
}
if i < 0 {
continue
}
- enc := strings.Replace(name[:i], "_", "/", -1)
- path, err := module.DecodePath(enc)
+ encPath := strings.Replace(name[:i], "_", "/", -1)
+ path, err := module.DecodePath(encPath)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "go proxy_test: %v", err)
+ continue
+ }
+ encVers := name[i+1:]
+ vers, err := module.DecodeVersion(encVers)
if err != nil {
fmt.Fprintf(os.Stderr, "go proxy_test: %v", err)
continue
}
- vers := name[i+1:]
modList = append(modList, module.Version{Path: path, Version: vers})
}
}
http.NotFound(w, r)
return
}
- vers, ext := file[:i], file[i+1:]
+ encVers, ext := file[:i], file[i+1:]
+ vers, err := module.DecodeVersion(encVers)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "go proxy_test: %v", err)
+ http.NotFound(w, r)
+ return
+ }
if codehost.AllHex(vers) {
var best string
fmt.Fprintf(os.Stderr, "go proxy: %v\n", err)
return nil
}
+ encVers, err := module.EncodeVersion(vers)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "go proxy: %v\n", err)
+ return nil
+ }
prefix := strings.Replace(enc, "/", "_", -1)
- name := filepath.Join(cmdGoDir, "testdata/mod", prefix+"_"+vers+".txt")
+ name := filepath.Join(cmdGoDir, "testdata/mod", prefix+"_"+encVers+".txt")
a := archiveCache.Do(name, func() interface{} {
a, err := txtar.ParseFile(name)
if err != nil {
-rsc.io/quote@v2.0.0 && cp mod/rsc.io_quote_v0.0.0-20180709153244-fd906ed3b100.txt mod/rsc.io_quote_v2.0.0.txt
+rsc.io/QUOTE v1.5.2
-- .mod --
module rsc.io/QUOTE
--- /dev/null
+rsc.io/QUOTE v1.5.3-PRE (sigh)
+
+-- .mod --
+module rsc.io/QUOTE
+
+require rsc.io/quote v1.5.2
+-- .info --
+{"Version":"v1.5.3-PRE","Name":"","Short":"","Time":"2018-07-15T16:25:34Z"}
+-- go.mod --
+module rsc.io/QUOTE
+
+require rsc.io/quote v1.5.2
+-- QUOTE/quote.go --
+// Copyright 2018 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// PACKAGE QUOTE COLLECTS LOUD SAYINGS.
+package QUOTE
+
+import (
+ "strings"
+
+ "rsc.io/quote"
+)
+
+// HELLO RETURNS A GREETING.
+func HELLO() string {
+ return strings.ToUpper(quote.Hello())
+}
+
+// GLASS RETURNS A USEFUL PHRASE FOR WORLD TRAVELERS.
+func GLASS() string {
+ return strings.ToUpper(quote.GLASS())
+}
+
+// GO RETURNS A GO PROVERB.
+func GO() string {
+ return strings.ToUpper(quote.GO())
+}
+
+// OPT RETURNS AN OPTIMIZATION TRUTH.
+func OPT() string {
+ return strings.ToUpper(quote.OPT())
+}
+-- QUOTE/quote_test.go --
+// Copyright 2018 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package QUOTE
+
+import (
+ "os"
+ "testing"
+)
+
+func init() {
+ os.Setenv("LC_ALL", "en")
+}
+
+func TestHELLO(t *testing.T) {
+ hello := "HELLO, WORLD"
+ if out := HELLO(); out != hello {
+ t.Errorf("HELLO() = %q, want %q", out, hello)
+ }
+}
+
+func TestGLASS(t *testing.T) {
+ glass := "I CAN EAT GLASS AND IT DOESN'T HURT ME."
+ if out := GLASS(); out != glass {
+ t.Errorf("GLASS() = %q, want %q", out, glass)
+ }
+}
+
+func TestGO(t *testing.T) {
+ go1 := "DON'T COMMUNICATE BY SHARING MEMORY, SHARE MEMORY BY COMMUNICATING."
+ if out := GO(); out != go1 {
+ t.Errorf("GO() = %q, want %q", out, go1)
+ }
+}
+
+func TestOPT(t *testing.T) {
+ opt := "IF A PROGRAM IS TOO SLOW, IT MUST HAVE A LOOP."
+ if out := OPT(); out != opt {
+ t.Errorf("OPT() = %q, want %q", out, opt)
+ }
+}
-rsc.io/quote@v2.0.0 && cp mod/rsc.io_quote_v0.0.0-20180709153244-fd906ed3b100.txt mod/rsc.io_quote_v2.0.0.txt
+rsc.io/quote@v2.0.0
-- .mod --
module "rsc.io/quote"
stdout 'DEPS.*rsc.io/quote'
stdout 'DIR.*!q!u!o!t!e'
+go get rsc.io/QUOTE@v1.5.3-PRE
+go list -m all
+stdout '^rsc.io/QUOTE v1.5.3-PRE'
+
+go list -f '{{.Dir}}' rsc.io/QUOTE/QUOTE
+stdout '!q!u!o!t!e@v1.5.3-!p!r!e'
+
-- go.mod --
module x