mirror of https://github.com/goproxyio/goproxy
init
parent
3f2adc0b09
commit
68c0e15704
|
@ -0,0 +1,76 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/goproxyio/goproxy/module"
|
||||||
|
)
|
||||||
|
|
||||||
|
var cacheDir string
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
gp := os.Getenv("GOPATH")
|
||||||
|
if gp == "" {
|
||||||
|
panic("can not find $GOPATH")
|
||||||
|
}
|
||||||
|
cacheDir = filepath.Join(gp, "pkg", "mod", "cache", "download")
|
||||||
|
http.Handle("/", mainHandler(http.FileServer(http.Dir(cacheDir))))
|
||||||
|
err := http.ListenAndServe(":8081", nil)
|
||||||
|
if nil != err {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func mainHandler(inner http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if _, err := os.Stat(filepath.Join(cacheDir, r.URL.Path)); err != nil {
|
||||||
|
if strings.HasSuffix(r.URL.Path, ".info") {
|
||||||
|
mod := strings.Split(r.URL.Path, "/@v/")
|
||||||
|
if len(mod) != 2 {
|
||||||
|
ReturnServerError(w, fmt.Errorf("bad module path:%s", r.URL.Path))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
version := strings.TrimSuffix(mod[1], ".info")
|
||||||
|
version, err = module.DecodeVersion(version)
|
||||||
|
if err != nil {
|
||||||
|
ReturnServerError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
path := strings.TrimPrefix(mod[0], "/")
|
||||||
|
path, err := module.DecodePath(path)
|
||||||
|
if err != nil {
|
||||||
|
ReturnServerError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
stdout, stderr, err := goGet(path + "@" + version)
|
||||||
|
if err != nil {
|
||||||
|
ReturnServerError(w, fmt.Errorf("stdout: %s stderr: %s", stdout, stderr))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if strings.HasSuffix(r.URL.Path, "/@v/list") {
|
||||||
|
w.WriteHeader(200)
|
||||||
|
w.Write([]byte(""))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
inner.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func goGet(path string) (string, string, error) {
|
||||||
|
fmt.Fprintf(os.Stdout, "goproxy: download %s\n", path)
|
||||||
|
cmd := exec.Command("go", "get", "-d", path)
|
||||||
|
var stdout bytes.Buffer
|
||||||
|
var stderr bytes.Buffer
|
||||||
|
cmd.Stdout = &stdout
|
||||||
|
cmd.Stderr = &stderr
|
||||||
|
err := cmd.Run()
|
||||||
|
return string(stdout.Bytes()), string(stderr.Bytes()), err
|
||||||
|
}
|
|
@ -0,0 +1,540 @@
|
||||||
|
// 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 module defines the module.Version type
|
||||||
|
// along with support code.
|
||||||
|
package module
|
||||||
|
|
||||||
|
// IMPORTANT NOTE
|
||||||
|
//
|
||||||
|
// This file essentially defines the set of valid import paths for the go command.
|
||||||
|
// There are many subtle considerations, including Unicode ambiguity,
|
||||||
|
// security, network, and file system representations.
|
||||||
|
//
|
||||||
|
// This file also defines the set of valid module path and version combinations,
|
||||||
|
// another topic with many subtle considerations.
|
||||||
|
//
|
||||||
|
// Changes to the semantics in this file require approval from rsc.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
|
"github.com/goproxyio/goproxy/semver"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A Version is defined by a module path and version pair.
|
||||||
|
type Version struct {
|
||||||
|
Path string
|
||||||
|
|
||||||
|
// Version is usually a semantic version in canonical form.
|
||||||
|
// There are two exceptions to this general rule.
|
||||||
|
// First, the top-level target of a build has no specific version
|
||||||
|
// and uses Version = "".
|
||||||
|
// Second, during MVS calculations the version "none" is used
|
||||||
|
// to represent the decision to take no version of a given module.
|
||||||
|
Version string `json:",omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check checks that a given module path, version pair is valid.
|
||||||
|
// In addition to the path being a valid module path
|
||||||
|
// and the version being a valid semantic version,
|
||||||
|
// the two must correspond.
|
||||||
|
// For example, the path "yaml/v2" only corresponds to
|
||||||
|
// semantic versions beginning with "v2.".
|
||||||
|
func Check(path, version string) error {
|
||||||
|
if err := CheckPath(path); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !semver.IsValid(version) {
|
||||||
|
return fmt.Errorf("malformed semantic version %v", version)
|
||||||
|
}
|
||||||
|
_, pathMajor, _ := SplitPathVersion(path)
|
||||||
|
if !MatchPathMajor(version, pathMajor) {
|
||||||
|
if pathMajor == "" {
|
||||||
|
pathMajor = "v0 or v1"
|
||||||
|
}
|
||||||
|
if pathMajor[0] == '.' { // .v1
|
||||||
|
pathMajor = pathMajor[1:]
|
||||||
|
}
|
||||||
|
return fmt.Errorf("mismatched module path %v and version %v (want %v)", path, version, pathMajor)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// firstPathOK reports whether r can appear in the first element of a module path.
|
||||||
|
// The first element of the path must be an LDH domain name, at least for now.
|
||||||
|
// To avoid case ambiguity, the domain name must be entirely lower case.
|
||||||
|
func firstPathOK(r rune) bool {
|
||||||
|
return r == '-' || r == '.' ||
|
||||||
|
'0' <= r && r <= '9' ||
|
||||||
|
'a' <= r && r <= 'z'
|
||||||
|
}
|
||||||
|
|
||||||
|
// pathOK reports whether r can appear in an import path element.
|
||||||
|
// Paths can be ASCII letters, ASCII digits, and limited ASCII punctuation: + - . _ and ~.
|
||||||
|
// This matches what "go get" has historically recognized in import paths.
|
||||||
|
// TODO(rsc): We would like to allow Unicode letters, but that requires additional
|
||||||
|
// care in the safe encoding (see note below).
|
||||||
|
func pathOK(r rune) bool {
|
||||||
|
if r < utf8.RuneSelf {
|
||||||
|
return r == '+' || r == '-' || r == '.' || r == '_' || r == '~' ||
|
||||||
|
'0' <= r && r <= '9' ||
|
||||||
|
'A' <= r && r <= 'Z' ||
|
||||||
|
'a' <= r && r <= 'z'
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// fileNameOK reports whether r can appear in a file name.
|
||||||
|
// For now we allow all Unicode letters but otherwise limit to pathOK plus a few more punctuation characters.
|
||||||
|
// If we expand the set of allowed characters here, we have to
|
||||||
|
// work harder at detecting potential case-folding and normalization collisions.
|
||||||
|
// See note about "safe encoding" below.
|
||||||
|
func fileNameOK(r rune) bool {
|
||||||
|
if r < utf8.RuneSelf {
|
||||||
|
// Entire set of ASCII punctuation, from which we remove characters:
|
||||||
|
// ! " # $ % & ' ( ) * + , - . / : ; < = > ? @ [ \ ] ^ _ ` { | } ~
|
||||||
|
// We disallow some shell special characters: " ' * < > ? ` |
|
||||||
|
// (Note that some of those are disallowed by the Windows file system as well.)
|
||||||
|
// We also disallow path separators / : and \ (fileNameOK is only called on path element characters).
|
||||||
|
// We allow spaces (U+0020) in file names.
|
||||||
|
const allowed = "!#$%&()+,-.=@[]^_{}~ "
|
||||||
|
if '0' <= r && r <= '9' || 'A' <= r && r <= 'Z' || 'a' <= r && r <= 'z' {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
for i := 0; i < len(allowed); i++ {
|
||||||
|
if rune(allowed[i]) == r {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// It may be OK to add more ASCII punctuation here, but only carefully.
|
||||||
|
// For example Windows disallows < > \, and macOS disallows :, so we must not allow those.
|
||||||
|
return unicode.IsLetter(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckPath checks that a module path is valid.
|
||||||
|
func CheckPath(path string) error {
|
||||||
|
if err := checkPath(path, false); err != nil {
|
||||||
|
return fmt.Errorf("malformed module path %q: %v", path, err)
|
||||||
|
}
|
||||||
|
i := strings.Index(path, "/")
|
||||||
|
if i < 0 {
|
||||||
|
i = len(path)
|
||||||
|
}
|
||||||
|
if i == 0 {
|
||||||
|
return fmt.Errorf("malformed module path %q: leading slash", path)
|
||||||
|
}
|
||||||
|
if !strings.Contains(path[:i], ".") {
|
||||||
|
return fmt.Errorf("malformed module path %q: missing dot in first path element", path)
|
||||||
|
}
|
||||||
|
if path[0] == '-' {
|
||||||
|
return fmt.Errorf("malformed module path %q: leading dash in first path element", path)
|
||||||
|
}
|
||||||
|
for _, r := range path[:i] {
|
||||||
|
if !firstPathOK(r) {
|
||||||
|
return fmt.Errorf("malformed module path %q: invalid char %q in first path element", path, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if _, _, ok := SplitPathVersion(path); !ok {
|
||||||
|
return fmt.Errorf("malformed module path %q: invalid version", path)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckImportPath checks that an import path is valid.
|
||||||
|
func CheckImportPath(path string) error {
|
||||||
|
if err := checkPath(path, false); err != nil {
|
||||||
|
return fmt.Errorf("malformed import path %q: %v", path, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkPath checks that a general path is valid.
|
||||||
|
// It returns an error describing why but not mentioning path.
|
||||||
|
// Because these checks apply to both module paths and import paths,
|
||||||
|
// the caller is expected to add the "malformed ___ path %q: " prefix.
|
||||||
|
// fileName indicates whether the final element of the path is a file name
|
||||||
|
// (as opposed to a directory name).
|
||||||
|
func checkPath(path string, fileName bool) error {
|
||||||
|
if !utf8.ValidString(path) {
|
||||||
|
return fmt.Errorf("invalid UTF-8")
|
||||||
|
}
|
||||||
|
if path == "" {
|
||||||
|
return fmt.Errorf("empty string")
|
||||||
|
}
|
||||||
|
if strings.Contains(path, "..") {
|
||||||
|
return fmt.Errorf("double dot")
|
||||||
|
}
|
||||||
|
if strings.Contains(path, "//") {
|
||||||
|
return fmt.Errorf("double slash")
|
||||||
|
}
|
||||||
|
if path[len(path)-1] == '/' {
|
||||||
|
return fmt.Errorf("trailing slash")
|
||||||
|
}
|
||||||
|
elemStart := 0
|
||||||
|
for i, r := range path {
|
||||||
|
if r == '/' {
|
||||||
|
if err := checkElem(path[elemStart:i], fileName); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
elemStart = i + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := checkElem(path[elemStart:], fileName); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkElem checks whether an individual path element is valid.
|
||||||
|
// fileName indicates whether the element is a file name (not a directory name).
|
||||||
|
func checkElem(elem string, fileName bool) error {
|
||||||
|
if elem == "" {
|
||||||
|
return fmt.Errorf("empty path element")
|
||||||
|
}
|
||||||
|
if strings.Count(elem, ".") == len(elem) {
|
||||||
|
return fmt.Errorf("invalid path element %q", elem)
|
||||||
|
}
|
||||||
|
if elem[0] == '.' && !fileName {
|
||||||
|
return fmt.Errorf("leading dot in path element")
|
||||||
|
}
|
||||||
|
if elem[len(elem)-1] == '.' {
|
||||||
|
return fmt.Errorf("trailing dot in path element")
|
||||||
|
}
|
||||||
|
charOK := pathOK
|
||||||
|
if fileName {
|
||||||
|
charOK = fileNameOK
|
||||||
|
}
|
||||||
|
for _, r := range elem {
|
||||||
|
if !charOK(r) {
|
||||||
|
return fmt.Errorf("invalid char %q", r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Windows disallows a bunch of path elements, sadly.
|
||||||
|
// See https://docs.microsoft.com/en-us/windows/desktop/fileio/naming-a-file
|
||||||
|
short := elem
|
||||||
|
if i := strings.Index(short, "."); i >= 0 {
|
||||||
|
short = short[:i]
|
||||||
|
}
|
||||||
|
for _, bad := range badWindowsNames {
|
||||||
|
if strings.EqualFold(bad, short) {
|
||||||
|
return fmt.Errorf("disallowed path element %q", elem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckFilePath checks whether a slash-separated file path is valid.
|
||||||
|
func CheckFilePath(path string) error {
|
||||||
|
if err := checkPath(path, true); err != nil {
|
||||||
|
return fmt.Errorf("malformed file path %q: %v", path, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// badWindowsNames are the reserved file path elements on Windows.
|
||||||
|
// See https://docs.microsoft.com/en-us/windows/desktop/fileio/naming-a-file
|
||||||
|
var badWindowsNames = []string{
|
||||||
|
"CON",
|
||||||
|
"PRN",
|
||||||
|
"AUX",
|
||||||
|
"NUL",
|
||||||
|
"COM1",
|
||||||
|
"COM2",
|
||||||
|
"COM3",
|
||||||
|
"COM4",
|
||||||
|
"COM5",
|
||||||
|
"COM6",
|
||||||
|
"COM7",
|
||||||
|
"COM8",
|
||||||
|
"COM9",
|
||||||
|
"LPT1",
|
||||||
|
"LPT2",
|
||||||
|
"LPT3",
|
||||||
|
"LPT4",
|
||||||
|
"LPT5",
|
||||||
|
"LPT6",
|
||||||
|
"LPT7",
|
||||||
|
"LPT8",
|
||||||
|
"LPT9",
|
||||||
|
}
|
||||||
|
|
||||||
|
// SplitPathVersion returns prefix and major version such that prefix+pathMajor == path
|
||||||
|
// and version is either empty or "/vN" for N >= 2.
|
||||||
|
// As a special case, gopkg.in paths are recognized directly;
|
||||||
|
// they require ".vN" instead of "/vN", and for all N, not just N >= 2.
|
||||||
|
func SplitPathVersion(path string) (prefix, pathMajor string, ok bool) {
|
||||||
|
if strings.HasPrefix(path, "gopkg.in/") {
|
||||||
|
return splitGopkgIn(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
i := len(path)
|
||||||
|
dot := false
|
||||||
|
for i > 0 && ('0' <= path[i-1] && path[i-1] <= '9' || path[i-1] == '.') {
|
||||||
|
if path[i-1] == '.' {
|
||||||
|
dot = true
|
||||||
|
}
|
||||||
|
i--
|
||||||
|
}
|
||||||
|
if i <= 1 || path[i-1] != 'v' || path[i-2] != '/' {
|
||||||
|
return path, "", true
|
||||||
|
}
|
||||||
|
prefix, pathMajor = path[:i-2], path[i-2:]
|
||||||
|
if dot || len(pathMajor) <= 2 || pathMajor[2] == '0' || pathMajor == "/v1" {
|
||||||
|
return path, "", false
|
||||||
|
}
|
||||||
|
return prefix, pathMajor, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// splitGopkgIn is like SplitPathVersion but only for gopkg.in paths.
|
||||||
|
func splitGopkgIn(path string) (prefix, pathMajor string, ok bool) {
|
||||||
|
if !strings.HasPrefix(path, "gopkg.in/") {
|
||||||
|
return path, "", false
|
||||||
|
}
|
||||||
|
i := len(path)
|
||||||
|
if strings.HasSuffix(path, "-unstable") {
|
||||||
|
i -= len("-unstable")
|
||||||
|
}
|
||||||
|
for i > 0 && ('0' <= path[i-1] && path[i-1] <= '9') {
|
||||||
|
i--
|
||||||
|
}
|
||||||
|
if i <= 1 || path[i-1] != 'v' || path[i-2] != '.' {
|
||||||
|
// All gopkg.in paths must end in vN for some N.
|
||||||
|
return path, "", false
|
||||||
|
}
|
||||||
|
prefix, pathMajor = path[:i-2], path[i-2:]
|
||||||
|
if len(pathMajor) <= 2 || pathMajor[2] == '0' && pathMajor != ".v0" {
|
||||||
|
return path, "", false
|
||||||
|
}
|
||||||
|
return prefix, pathMajor, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// MatchPathMajor reports whether the semantic version v
|
||||||
|
// matches the path major version pathMajor.
|
||||||
|
func MatchPathMajor(v, pathMajor string) bool {
|
||||||
|
if strings.HasPrefix(pathMajor, ".v") && strings.HasSuffix(pathMajor, "-unstable") {
|
||||||
|
pathMajor = strings.TrimSuffix(pathMajor, "-unstable")
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(v, "v0.0.0-") && pathMajor == ".v1" {
|
||||||
|
// Allow old bug in pseudo-versions that generated v0.0.0- pseudoversion for gopkg .v1.
|
||||||
|
// For example, gopkg.in/yaml.v2@v2.2.1's go.mod requires gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405.
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
m := semver.Major(v)
|
||||||
|
if pathMajor == "" {
|
||||||
|
return m == "v0" || m == "v1" || semver.Build(v) == "+incompatible"
|
||||||
|
}
|
||||||
|
return (pathMajor[0] == '/' || pathMajor[0] == '.') && m == pathMajor[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// CanonicalVersion returns the canonical form of the version string v.
|
||||||
|
// It is the same as semver.Canonical(v) except that it preserves the special build suffix "+incompatible".
|
||||||
|
func CanonicalVersion(v string) string {
|
||||||
|
cv := semver.Canonical(v)
|
||||||
|
if semver.Build(v) == "+incompatible" {
|
||||||
|
cv += "+incompatible"
|
||||||
|
}
|
||||||
|
return cv
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort sorts the list by Path, breaking ties by comparing Versions.
|
||||||
|
func Sort(list []Version) {
|
||||||
|
sort.Slice(list, func(i, j int) bool {
|
||||||
|
mi := list[i]
|
||||||
|
mj := list[j]
|
||||||
|
if mi.Path != mj.Path {
|
||||||
|
return mi.Path < mj.Path
|
||||||
|
}
|
||||||
|
// To help go.sum formatting, allow version/file.
|
||||||
|
// Compare semver prefix by semver rules,
|
||||||
|
// file by string order.
|
||||||
|
vi := mi.Version
|
||||||
|
vj := mj.Version
|
||||||
|
var fi, fj string
|
||||||
|
if k := strings.Index(vi, "/"); k >= 0 {
|
||||||
|
vi, fi = vi[:k], vi[k:]
|
||||||
|
}
|
||||||
|
if k := strings.Index(vj, "/"); k >= 0 {
|
||||||
|
vj, fj = vj[:k], vj[k:]
|
||||||
|
}
|
||||||
|
if vi != vj {
|
||||||
|
return semver.Compare(vi, vj) < 0
|
||||||
|
}
|
||||||
|
return fi < fj
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Safe encodings
|
||||||
|
//
|
||||||
|
// Module paths appear as substrings of file system paths
|
||||||
|
// (in the download cache) and of web server URLs in the proxy protocol.
|
||||||
|
// In general we cannot rely on file systems to be case-sensitive,
|
||||||
|
// nor can we rely on web servers, since they read from file systems.
|
||||||
|
// That is, we cannot rely on the file system to keep rsc.io/QUOTE
|
||||||
|
// and rsc.io/quote separate. Windows and macOS don't.
|
||||||
|
// Instead, we must never require two different casings of a file path.
|
||||||
|
// Because we want the download cache to match the proxy protocol,
|
||||||
|
// and because we want the proxy protocol to be possible to serve
|
||||||
|
// from a tree of static files (which might be stored on a case-insensitive
|
||||||
|
// file system), the proxy protocol must never require two different casings
|
||||||
|
// of a URL path either.
|
||||||
|
//
|
||||||
|
// One possibility would be to make the safe encoding be the lowercase
|
||||||
|
// hexadecimal encoding of the actual path bytes. This would avoid ever
|
||||||
|
// needing different casings of a file path, but it would be fairly illegible
|
||||||
|
// to most programmers when those paths appeared in the file system
|
||||||
|
// (including in file paths in compiler errors and stack traces)
|
||||||
|
// in web server logs, and so on. Instead, we want a safe encoding that
|
||||||
|
// leaves most paths unaltered.
|
||||||
|
//
|
||||||
|
// The safe encoding is this:
|
||||||
|
// replace every uppercase letter with an exclamation mark
|
||||||
|
// followed by the letter's lowercase equivalent.
|
||||||
|
//
|
||||||
|
// For example,
|
||||||
|
// github.com/Azure/azure-sdk-for-go -> github.com/!azure/azure-sdk-for-go.
|
||||||
|
// github.com/GoogleCloudPlatform/cloudsql-proxy -> github.com/!google!cloud!platform/cloudsql-proxy
|
||||||
|
// github.com/Sirupsen/logrus -> github.com/!sirupsen/logrus.
|
||||||
|
//
|
||||||
|
// Import paths that avoid upper-case letters are left unchanged.
|
||||||
|
// Note that because import paths are ASCII-only and avoid various
|
||||||
|
// problematic punctuation (like : < and >), the safe encoding is also ASCII-only
|
||||||
|
// and avoids the same problematic punctuation.
|
||||||
|
//
|
||||||
|
// Import paths have never allowed exclamation marks, so there is no
|
||||||
|
// need to define how to encode a literal !.
|
||||||
|
//
|
||||||
|
// Although paths are disallowed from using Unicode (see pathOK above),
|
||||||
|
// the eventual plan is to allow Unicode letters as well, to assume that
|
||||||
|
// file systems and URLs are Unicode-safe (storing UTF-8), and apply
|
||||||
|
// the !-for-uppercase convention. Note however that not all runes that
|
||||||
|
// are different but case-fold equivalent are an upper/lower pair.
|
||||||
|
// For example, U+004B ('K'), U+006B ('k'), and U+212A ('K' for Kelvin)
|
||||||
|
// are considered to case-fold to each other. When we do add Unicode
|
||||||
|
// letters, we must not assume that upper/lower are the only case-equivalent pairs.
|
||||||
|
// Perhaps the Kelvin symbol would be disallowed entirely, for example.
|
||||||
|
// Or perhaps it would encode as "!!k", or perhaps as "(212A)".
|
||||||
|
//
|
||||||
|
// Also, it would be nice to allow Unicode marks as well as letters,
|
||||||
|
// but marks include combining marks, and then we must deal not
|
||||||
|
// only with case folding but also normalization: both U+00E9 ('é')
|
||||||
|
// and U+0065 U+0301 ('e' followed by combining acute accent)
|
||||||
|
// look the same on the page and are treated by some file systems
|
||||||
|
// as the same path. If we do allow Unicode marks in paths, there
|
||||||
|
// must be some kind of normalization to allow only one canonical
|
||||||
|
// encoding of any character used in an import path.
|
||||||
|
|
||||||
|
// EncodePath returns the safe encoding of the given module path.
|
||||||
|
// It fails if the module path is invalid.
|
||||||
|
func EncodePath(path string) (encoding string, err error) {
|
||||||
|
if err := CheckPath(path); err != nil {
|
||||||
|
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 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.
|
||||||
|
return "", fmt.Errorf("internal error: inconsistency in EncodePath")
|
||||||
|
}
|
||||||
|
if 'A' <= r && r <= 'Z' {
|
||||||
|
haveUpper = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !haveUpper {
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf []byte
|
||||||
|
for _, r := range s {
|
||||||
|
if 'A' <= r && r <= 'Z' {
|
||||||
|
buf = append(buf, '!', byte(r+'a'-'A'))
|
||||||
|
} else {
|
||||||
|
buf = append(buf, byte(r))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return string(buf), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodePath returns the module path of the given safe encoding.
|
||||||
|
// 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 {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
if bang {
|
||||||
|
bang = false
|
||||||
|
if r < 'a' || 'z' < r {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
buf = append(buf, byte(r+'A'-'a'))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if r == '!' {
|
||||||
|
bang = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if 'A' <= r && r <= 'Z' {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
buf = append(buf, byte(r))
|
||||||
|
}
|
||||||
|
if bang {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
return string(buf), true
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ReturnServerError(w http.ResponseWriter, err error) {
|
||||||
|
w.WriteHeader(500)
|
||||||
|
msg := fmt.Sprintf("%v", err)
|
||||||
|
w.Write([]byte(msg))
|
||||||
|
fmt.Fprintf(os.Stderr, "goproxy: %v\n", err)
|
||||||
|
}
|
|
@ -0,0 +1,388 @@
|
||||||
|
// 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 semver implements comparison of semantic version strings.
|
||||||
|
// In this package, semantic version strings must begin with a leading "v",
|
||||||
|
// as in "v1.0.0".
|
||||||
|
//
|
||||||
|
// The general form of a semantic version string accepted by this package is
|
||||||
|
//
|
||||||
|
// vMAJOR[.MINOR[.PATCH[-PRERELEASE][+BUILD]]]
|
||||||
|
//
|
||||||
|
// where square brackets indicate optional parts of the syntax;
|
||||||
|
// MAJOR, MINOR, and PATCH are decimal integers without extra leading zeros;
|
||||||
|
// PRERELEASE and BUILD are each a series of non-empty dot-separated identifiers
|
||||||
|
// using only alphanumeric characters and hyphens; and
|
||||||
|
// all-numeric PRERELEASE identifiers must not have leading zeros.
|
||||||
|
//
|
||||||
|
// This package follows Semantic Versioning 2.0.0 (see semver.org)
|
||||||
|
// with two exceptions. First, it requires the "v" prefix. Second, it recognizes
|
||||||
|
// vMAJOR and vMAJOR.MINOR (with no prerelease or build suffixes)
|
||||||
|
// as shorthands for vMAJOR.0.0 and vMAJOR.MINOR.0.
|
||||||
|
package semver
|
||||||
|
|
||||||
|
// parsed returns the parsed form of a semantic version string.
|
||||||
|
type parsed struct {
|
||||||
|
major string
|
||||||
|
minor string
|
||||||
|
patch string
|
||||||
|
short string
|
||||||
|
prerelease string
|
||||||
|
build string
|
||||||
|
err string
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsValid reports whether v is a valid semantic version string.
|
||||||
|
func IsValid(v string) bool {
|
||||||
|
_, ok := parse(v)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// Canonical returns the canonical formatting of the semantic version v.
|
||||||
|
// It fills in any missing .MINOR or .PATCH and discards build metadata.
|
||||||
|
// Two semantic versions compare equal only if their canonical formattings
|
||||||
|
// are identical strings.
|
||||||
|
// The canonical invalid semantic version is the empty string.
|
||||||
|
func Canonical(v string) string {
|
||||||
|
p, ok := parse(v)
|
||||||
|
if !ok {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
if p.build != "" {
|
||||||
|
return v[:len(v)-len(p.build)]
|
||||||
|
}
|
||||||
|
if p.short != "" {
|
||||||
|
return v + p.short
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Major returns the major version prefix of the semantic version v.
|
||||||
|
// For example, Major("v2.1.0") == "v2".
|
||||||
|
// If v is an invalid semantic version string, Major returns the empty string.
|
||||||
|
func Major(v string) string {
|
||||||
|
pv, ok := parse(v)
|
||||||
|
if !ok {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return v[:1+len(pv.major)]
|
||||||
|
}
|
||||||
|
|
||||||
|
// MajorMinor returns the major.minor version prefix of the semantic version v.
|
||||||
|
// For example, MajorMinor("v2.1.0") == "v2.1".
|
||||||
|
// If v is an invalid semantic version string, MajorMinor returns the empty string.
|
||||||
|
func MajorMinor(v string) string {
|
||||||
|
pv, ok := parse(v)
|
||||||
|
if !ok {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
i := 1 + len(pv.major)
|
||||||
|
if j := i + 1 + len(pv.minor); j <= len(v) && v[i] == '.' && v[i+1:j] == pv.minor {
|
||||||
|
return v[:j]
|
||||||
|
}
|
||||||
|
return v[:i] + "." + pv.minor
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prerelease returns the prerelease suffix of the semantic version v.
|
||||||
|
// For example, Prerelease("v2.1.0-pre+meta") == "-pre".
|
||||||
|
// If v is an invalid semantic version string, Prerelease returns the empty string.
|
||||||
|
func Prerelease(v string) string {
|
||||||
|
pv, ok := parse(v)
|
||||||
|
if !ok {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return pv.prerelease
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build returns the build suffix of the semantic version v.
|
||||||
|
// For example, Build("v2.1.0+meta") == "+meta".
|
||||||
|
// If v is an invalid semantic version string, Build returns the empty string.
|
||||||
|
func Build(v string) string {
|
||||||
|
pv, ok := parse(v)
|
||||||
|
if !ok {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return pv.build
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compare returns an integer comparing two versions according to
|
||||||
|
// according to semantic version precedence.
|
||||||
|
// The result will be 0 if v == w, -1 if v < w, or +1 if v > w.
|
||||||
|
//
|
||||||
|
// An invalid semantic version string is considered less than a valid one.
|
||||||
|
// All invalid semantic version strings compare equal to each other.
|
||||||
|
func Compare(v, w string) int {
|
||||||
|
pv, ok1 := parse(v)
|
||||||
|
pw, ok2 := parse(w)
|
||||||
|
if !ok1 && !ok2 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
if !ok1 {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
if !ok2 {
|
||||||
|
return +1
|
||||||
|
}
|
||||||
|
if c := compareInt(pv.major, pw.major); c != 0 {
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
if c := compareInt(pv.minor, pw.minor); c != 0 {
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
if c := compareInt(pv.patch, pw.patch); c != 0 {
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
return comparePrerelease(pv.prerelease, pw.prerelease)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Max canonicalizes its arguments and then returns the version string
|
||||||
|
// that compares greater.
|
||||||
|
func Max(v, w string) string {
|
||||||
|
v = Canonical(v)
|
||||||
|
w = Canonical(w)
|
||||||
|
if Compare(v, w) > 0 {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return w
|
||||||
|
}
|
||||||
|
|
||||||
|
func parse(v string) (p parsed, ok bool) {
|
||||||
|
if v == "" || v[0] != 'v' {
|
||||||
|
p.err = "missing v prefix"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p.major, v, ok = parseInt(v[1:])
|
||||||
|
if !ok {
|
||||||
|
p.err = "bad major version"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if v == "" {
|
||||||
|
p.minor = "0"
|
||||||
|
p.patch = "0"
|
||||||
|
p.short = ".0.0"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if v[0] != '.' {
|
||||||
|
p.err = "bad minor prefix"
|
||||||
|
ok = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p.minor, v, ok = parseInt(v[1:])
|
||||||
|
if !ok {
|
||||||
|
p.err = "bad minor version"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if v == "" {
|
||||||
|
p.patch = "0"
|
||||||
|
p.short = ".0"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if v[0] != '.' {
|
||||||
|
p.err = "bad patch prefix"
|
||||||
|
ok = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p.patch, v, ok = parseInt(v[1:])
|
||||||
|
if !ok {
|
||||||
|
p.err = "bad patch version"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(v) > 0 && v[0] == '-' {
|
||||||
|
p.prerelease, v, ok = parsePrerelease(v)
|
||||||
|
if !ok {
|
||||||
|
p.err = "bad prerelease"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(v) > 0 && v[0] == '+' {
|
||||||
|
p.build, v, ok = parseBuild(v)
|
||||||
|
if !ok {
|
||||||
|
p.err = "bad build"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if v != "" {
|
||||||
|
p.err = "junk on end"
|
||||||
|
ok = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ok = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseInt(v string) (t, rest string, ok bool) {
|
||||||
|
if v == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if v[0] < '0' || '9' < v[0] {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
i := 1
|
||||||
|
for i < len(v) && '0' <= v[i] && v[i] <= '9' {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
if v[0] == '0' && i != 1 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return v[:i], v[i:], true
|
||||||
|
}
|
||||||
|
|
||||||
|
func parsePrerelease(v string) (t, rest string, ok bool) {
|
||||||
|
// "A pre-release version MAY be denoted by appending a hyphen and
|
||||||
|
// a series of dot separated identifiers immediately following the patch version.
|
||||||
|
// Identifiers MUST comprise only ASCII alphanumerics and hyphen [0-9A-Za-z-].
|
||||||
|
// Identifiers MUST NOT be empty. Numeric identifiers MUST NOT include leading zeroes."
|
||||||
|
if v == "" || v[0] != '-' {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
i := 1
|
||||||
|
start := 1
|
||||||
|
for i < len(v) && v[i] != '+' {
|
||||||
|
if !isIdentChar(v[i]) && v[i] != '.' {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if v[i] == '.' {
|
||||||
|
if start == i || isBadNum(v[start:i]) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
start = i + 1
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
if start == i || isBadNum(v[start:i]) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return v[:i], v[i:], true
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseBuild(v string) (t, rest string, ok bool) {
|
||||||
|
if v == "" || v[0] != '+' {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
i := 1
|
||||||
|
start := 1
|
||||||
|
for i < len(v) {
|
||||||
|
if !isIdentChar(v[i]) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if v[i] == '.' {
|
||||||
|
if start == i {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
start = i + 1
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
if start == i {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return v[:i], v[i:], true
|
||||||
|
}
|
||||||
|
|
||||||
|
func isIdentChar(c byte) bool {
|
||||||
|
return 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || '0' <= c && c <= '9' || c == '-'
|
||||||
|
}
|
||||||
|
|
||||||
|
func isBadNum(v string) bool {
|
||||||
|
i := 0
|
||||||
|
for i < len(v) && '0' <= v[i] && v[i] <= '9' {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
return i == len(v) && i > 1 && v[0] == '0'
|
||||||
|
}
|
||||||
|
|
||||||
|
func isNum(v string) bool {
|
||||||
|
i := 0
|
||||||
|
for i < len(v) && '0' <= v[i] && v[i] <= '9' {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
return i == len(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func compareInt(x, y string) int {
|
||||||
|
if x == y {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
if len(x) < len(y) {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
if len(x) > len(y) {
|
||||||
|
return +1
|
||||||
|
}
|
||||||
|
if x < y {
|
||||||
|
return -1
|
||||||
|
} else {
|
||||||
|
return +1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func comparePrerelease(x, y string) int {
|
||||||
|
// "When major, minor, and patch are equal, a pre-release version has
|
||||||
|
// lower precedence than a normal version.
|
||||||
|
// Example: 1.0.0-alpha < 1.0.0.
|
||||||
|
// Precedence for two pre-release versions with the same major, minor,
|
||||||
|
// and patch version MUST be determined by comparing each dot separated
|
||||||
|
// identifier from left to right until a difference is found as follows:
|
||||||
|
// identifiers consisting of only digits are compared numerically and
|
||||||
|
// identifiers with letters or hyphens are compared lexically in ASCII
|
||||||
|
// sort order. Numeric identifiers always have lower precedence than
|
||||||
|
// non-numeric identifiers. A larger set of pre-release fields has a
|
||||||
|
// higher precedence than a smaller set, if all of the preceding
|
||||||
|
// identifiers are equal.
|
||||||
|
// Example: 1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.beta <
|
||||||
|
// 1.0.0-beta < 1.0.0-beta.2 < 1.0.0-beta.11 < 1.0.0-rc.1 < 1.0.0."
|
||||||
|
if x == y {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
if x == "" {
|
||||||
|
return +1
|
||||||
|
}
|
||||||
|
if y == "" {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
for x != "" && y != "" {
|
||||||
|
x = x[1:] // skip - or .
|
||||||
|
y = y[1:] // skip - or .
|
||||||
|
var dx, dy string
|
||||||
|
dx, x = nextIdent(x)
|
||||||
|
dy, y = nextIdent(y)
|
||||||
|
if dx != dy {
|
||||||
|
ix := isNum(dx)
|
||||||
|
iy := isNum(dy)
|
||||||
|
if ix != iy {
|
||||||
|
if ix {
|
||||||
|
return -1
|
||||||
|
} else {
|
||||||
|
return +1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ix {
|
||||||
|
if len(dx) < len(dy) {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
if len(dx) > len(dy) {
|
||||||
|
return +1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if dx < dy {
|
||||||
|
return -1
|
||||||
|
} else {
|
||||||
|
return +1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if x == "" {
|
||||||
|
return -1
|
||||||
|
} else {
|
||||||
|
return +1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func nextIdent(x string) (dx, rest string) {
|
||||||
|
i := 0
|
||||||
|
for i < len(x) && x[i] != '.' {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
return x[:i], x[i:]
|
||||||
|
}
|
Loading…
Reference in New Issue