X-Git-Url: http://www.git.cypherpunks.ru/?p=gocheese.git;a=blobdiff_plain;f=main.go;h=76f4b8a9908936e14114e45fcf92a2d611eda1f3;hp=f4a4359e677f63e54f5723f6bd06fa5d8fc6a83d;hb=60834a0713d5dcc6a9911511cb8618ce7358c824;hpb=948543909470b983c001f13fb267a64070dd20ae diff --git a/main.go b/main.go index f4a4359..76f4b8a 100644 --- a/main.go +++ b/main.go @@ -28,7 +28,6 @@ import ( "errors" "flag" "fmt" - "io/ioutil" "log" "net" "net/http" @@ -36,7 +35,6 @@ import ( "os" "os/signal" "path/filepath" - "regexp" "runtime" "strings" "syscall" @@ -48,18 +46,6 @@ import ( const ( Version = "3.0.0" UserAgent = "GoCheese/" + Version - HTMLBegin = ` - - - - Links for %s - - -` - HTMLEnd = " \n\n" - HTMLElement = " %s\n" - InternalFlag = ".internal" - GPGSigExt = ".asc" Warranty = `This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -74,155 +60,39 @@ You should have received a copy of the GNU General Public License along with this program. If not, see .` ) -const ( - HashAlgoSHA256 = "sha256" - HashAlgoBLAKE2b256 = "blake2_256" - HashAlgoSHA512 = "sha512" - HashAlgoMD5 = "md5" -) - var ( - pkgPyPI = regexp.MustCompile(`^.*]*>(.+)
.*$`) - normalizationRe = regexp.MustCompilePOSIX("[-_.]+") - - knownHashAlgos []string = []string{ - HashAlgoSHA256, - HashAlgoBLAKE2b256, - HashAlgoSHA512, - HashAlgoMD5, - } + Root = flag.String("root", "./packages", "Path to packages directory") + Bind = flag.String("bind", "[::]:8080", "Address to bind to") + MaxClients = flag.Int("maxclients", 128, "Maximal amount of simultaneous clients") + DoUCSPI = flag.Bool("ucspi", false, "Work as UCSPI-TCP service") - root = flag.String("root", "./packages", "Path to packages directory") - bind = flag.String("bind", "[::]:8080", "Address to bind to") - maxClients = flag.Int("maxclients", 128, "Maximal amount of simultaneous clients") - doUCSPI = flag.Bool("ucspi", false, "Work as UCSPI-TCP service") + TLSCert = flag.String("tls-cert", "", "Path to TLS X.509 certificate") + TLSKey = flag.String("tls-key", "", "Path to TLS X.509 private key") - tlsCert = flag.String("tls-cert", "", "Path to TLS X.509 certificate") - tlsKey = flag.String("tls-key", "", "Path to TLS X.509 private key") + NoRefreshURLPath = flag.String("norefresh", "/norefresh/", "Non-refreshing URL path") + RefreshURLPath = flag.String("refresh", "/simple/", "Auto-refreshing URL path") + GPGUpdateURLPath = flag.String("gpgupdate", "/gpgupdate/", "GPG forceful refreshing URL path") + JSONURLPath = flag.String("json", "/pypi/", "JSON API URL path") - norefreshURLPath = flag.String("norefresh", "/norefresh/", "Non-refreshing URL path") - refreshURLPath = flag.String("refresh", "/simple/", "Auto-refreshing URL path") - gpgUpdateURLPath = flag.String("gpgupdate", "/gpgupdate/", "GPG forceful refreshing URL path") + PyPIURL = flag.String("pypi", "https://pypi.org/simple/", "Upstream (PyPI) URL") + PyPICertHash = flag.String("pypi-cert-hash", "", "Authenticate upstream by its X.509 certificate's SPKI SHA256 hash") + JSONURL = flag.String("pypi-json", "https://pypi.org/pypi/", "Enable and use specified JSON API upstream URL") - pypiURL = flag.String("pypi", "https://pypi.org/simple/", "Upstream (PyPI) URL") - pypiCertHash = flag.String("pypi-cert-hash", "", "Authenticate upstream by its X.509 certificate's SPKI SHA256 hash") + PasswdPath = flag.String("passwd", "", "Path to FIFO for upload authentication") + PasswdListPath = flag.String("passwd-list", "", "Path to FIFO for login listing") + PasswdCheck = flag.Bool("passwd-check", false, "Run password checker") - passwdPath = flag.String("passwd", "", "Path to FIFO for upload authentication") - passwdListPath = flag.String("passwd-list", "", "Path to FIFO for login listing") - passwdCheck = flag.Bool("passwd-check", false, "Run password checker") + LogTimestamped = flag.Bool("log-timestamped", false, "Prepend timestmap to log messages") + FSCK = flag.Bool("fsck", false, "Check integrity of all packages (errors are in stderr)") + DoVersion = flag.Bool("version", false, "Print version information") + DoWarranty = flag.Bool("warranty", false, "Print warranty information") - logTimestamped = flag.Bool("log-timestamped", false, "Prepend timestmap to log messages") - fsck = flag.Bool("fsck", false, "Check integrity of all packages (errors are in stderr)") - version = flag.Bool("version", false, "Print version information") - warranty = flag.Bool("warranty", false, "Print warranty information") - - killed bool - pypiURLParsed *url.URL + Killed bool ) -func mkdirForPkg(w http.ResponseWriter, r *http.Request, pkgName string) bool { - path := filepath.Join(*root, pkgName) - if _, err := os.Stat(path); os.IsNotExist(err) { - if err = os.Mkdir(path, os.FileMode(0777)); err != nil { - log.Println("error", r.RemoteAddr, "mkdir", pkgName, err) - http.Error(w, err.Error(), http.StatusInternalServerError) - return false - } - log.Println(r.RemoteAddr, "mkdir", pkgName) - } - return true -} - -func listRoot(w http.ResponseWriter, r *http.Request) { - files, err := ioutil.ReadDir(*root) - if err != nil { - log.Println("error", r.RemoteAddr, "root", err) - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - var result bytes.Buffer - result.WriteString(fmt.Sprintf(HTMLBegin, "root")) - for _, file := range files { - if file.Mode().IsDir() { - result.WriteString(fmt.Sprintf( - HTMLElement, - *refreshURLPath+file.Name()+"/", - "", file.Name(), - )) - } - } - result.WriteString(HTMLEnd) - w.Write(result.Bytes()) -} - -func listDir( - w http.ResponseWriter, - r *http.Request, - pkgName string, - autorefresh, gpgUpdate bool, -) { - dirPath := filepath.Join(*root, pkgName) - if autorefresh { - if !refreshDir(w, r, pkgName, "", gpgUpdate) { - return - } - } else if _, err := os.Stat(dirPath); os.IsNotExist(err) && !refreshDir(w, r, pkgName, "", false) { - return - } - fis, err := ioutil.ReadDir(dirPath) - if err != nil { - log.Println("error", r.RemoteAddr, "list", pkgName, err) - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - files := make(map[string]struct{}, len(fis)/2) - for _, fi := range fis { - files[fi.Name()] = struct{}{} - } - var result bytes.Buffer - result.WriteString(fmt.Sprintf(HTMLBegin, pkgName)) - for _, algo := range knownHashAlgos { - for fn := range files { - if killed { - // Skip expensive I/O when shutting down - http.Error(w, "shutting down", http.StatusInternalServerError) - return - } - if !strings.HasSuffix(fn, "."+algo) { - continue - } - delete(files, fn) - digest, err := ioutil.ReadFile(filepath.Join(dirPath, fn)) - if err != nil { - log.Println("error", r.RemoteAddr, "list", fn, err) - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - fnClean := strings.TrimSuffix(fn, "."+algo) - delete(files, fnClean) - gpgSigAttr := "" - if _, err = os.Stat(filepath.Join(dirPath, fnClean+GPGSigExt)); err == nil { - gpgSigAttr = " data-gpg-sig=true" - delete(files, fnClean+GPGSigExt) - } - result.WriteString(fmt.Sprintf( - HTMLElement, - strings.Join([]string{ - *refreshURLPath, pkgName, "/", fnClean, - "#", algo, "=", hex.EncodeToString(digest), - }, ""), - gpgSigAttr, - fnClean, - )) - } - } - result.WriteString(HTMLEnd) - w.Write(result.Bytes()) -} - func servePkg(w http.ResponseWriter, r *http.Request, pkgName, filename string) { log.Println(r.RemoteAddr, "get", filename) - path := filepath.Join(*root, pkgName, filename) + path := filepath.Join(*Root, pkgName, filename) if _, err := os.Stat(path); os.IsNotExist(err) { if !refreshDir(w, r, pkgName, filename, false) { return @@ -238,13 +108,13 @@ func handler(w http.ResponseWriter, r *http.Request) { var path string var autorefresh bool var gpgUpdate bool - if strings.HasPrefix(r.URL.Path, *norefreshURLPath) { - path = strings.TrimPrefix(r.URL.Path, *norefreshURLPath) - } else if strings.HasPrefix(r.URL.Path, *refreshURLPath) { - path = strings.TrimPrefix(r.URL.Path, *refreshURLPath) + if strings.HasPrefix(r.URL.Path, *NoRefreshURLPath) { + path = strings.TrimPrefix(r.URL.Path, *NoRefreshURLPath) + } else if strings.HasPrefix(r.URL.Path, *RefreshURLPath) { + path = strings.TrimPrefix(r.URL.Path, *RefreshURLPath) autorefresh = true - } else if strings.HasPrefix(r.URL.Path, *gpgUpdateURLPath) { - path = strings.TrimPrefix(r.URL.Path, *gpgUpdateURLPath) + } else if strings.HasPrefix(r.URL.Path, *GPGUpdateURLPath) { + path = strings.TrimPrefix(r.URL.Path, *GPGUpdateURLPath) autorefresh = true gpgUpdate = true } else { @@ -260,7 +130,7 @@ func handler(w http.ResponseWriter, r *http.Request) { if parts[0] == "" { listRoot(w, r) } else { - listDir(w, r, parts[0], autorefresh, gpgUpdate) + serveListDir(w, r, parts[0], autorefresh, gpgUpdate) } } else { servePkg(w, r, parts[0], parts[1]) @@ -274,32 +144,32 @@ func handler(w http.ResponseWriter, r *http.Request) { func main() { flag.Parse() - if *warranty { + if *DoWarranty { fmt.Println(Warranty) return } - if *version { + if *DoVersion { fmt.Println("GoCheese", Version, "built with", runtime.Version()) return } - if *logTimestamped { + if *LogTimestamped { log.SetFlags(log.Ldate | log.Lmicroseconds | log.Lshortfile) } else { log.SetFlags(log.Lshortfile) } - if !*doUCSPI { + if !*DoUCSPI { log.SetOutput(os.Stdout) } - if *fsck { + if *FSCK { if !goodIntegrity() { os.Exit(1) } return } - if *passwdCheck { + if *PasswdCheck { if passwdReader(os.Stdin) { os.Exit(0) } else { @@ -307,11 +177,11 @@ func main() { } } - if *passwdPath != "" { + if *PasswdPath != "" { go func() { for { fd, err := os.OpenFile( - *passwdPath, + *PasswdPath, os.O_RDONLY, os.FileMode(0666), ) @@ -323,11 +193,11 @@ func main() { } }() } - if *passwdListPath != "" { + if *PasswdListPath != "" { go func() { for { fd, err := os.OpenFile( - *passwdListPath, + *PasswdListPath, os.O_WRONLY|os.O_APPEND, os.FileMode(0666), ) @@ -340,12 +210,12 @@ func main() { }() } - if (*tlsCert != "" && *tlsKey == "") || (*tlsCert == "" && *tlsKey != "") { + if (*TLSCert != "" && *TLSKey == "") || (*TLSCert == "" && *TLSKey != "") { log.Fatalln("Both -tls-cert and -tls-key are required") } var err error - pypiURLParsed, err = url.Parse(*pypiURL) + PyPIURLParsed, err = url.Parse(*PyPIURL) if err != nil { log.Fatalln(err) } @@ -353,12 +223,12 @@ func main() { ClientSessionCache: tls.NewLRUClientSessionCache(16), NextProtos: []string{"h2", "http/1.1"}, } - pypiHTTPTransport = http.Transport{ + PyPIHTTPTransport = http.Transport{ ForceAttemptHTTP2: true, TLSClientConfig: &tlsConfig, } - if *pypiCertHash != "" { - ourDgst, err := hex.DecodeString(*pypiCertHash) + if *PyPICertHash != "" { + ourDgst, err := hex.DecodeString(*PyPICertHash) if err != nil { log.Fatalln(err) } @@ -376,13 +246,16 @@ func main() { ReadTimeout: time.Minute, WriteTimeout: time.Minute, } - http.HandleFunc(*norefreshURLPath, handler) - http.HandleFunc(*refreshURLPath, handler) - if *gpgUpdateURLPath != "" { - http.HandleFunc(*gpgUpdateURLPath, handler) + http.HandleFunc("/", serveHRRoot) + http.HandleFunc("/hr/", serveHRPkg) + http.HandleFunc(*JSONURLPath, serveJSON) + http.HandleFunc(*NoRefreshURLPath, handler) + http.HandleFunc(*RefreshURLPath, handler) + if *GPGUpdateURLPath != "" { + http.HandleFunc(*GPGUpdateURLPath, handler) } - if *doUCSPI { + if *DoUCSPI { server.SetKeepAlivesEnabled(false) ln := &UCSPI{} server.ConnState = connStater @@ -394,18 +267,18 @@ func main() { return } - ln, err := net.Listen("tcp", *bind) + ln, err := net.Listen("tcp", *Bind) if err != nil { log.Fatal(err) } - ln = netutil.LimitListener(ln, *maxClients) + ln = netutil.LimitListener(ln, *MaxClients) needsShutdown := make(chan os.Signal, 0) exitErr := make(chan error, 0) signal.Notify(needsShutdown, syscall.SIGTERM, syscall.SIGINT) go func(s *http.Server) { <-needsShutdown - killed = true + Killed = true log.Println("shutting down") ctx, cancel := context.WithTimeout(context.TODO(), time.Minute) exitErr <- s.Shutdown(ctx) @@ -413,15 +286,17 @@ func main() { }(server) log.Println( - "GoCheese", Version, "listens:", - "root:", *root, - "bind:", *bind, - "pypi:", *pypiURL, + UserAgent, "ready:", + "root:", *Root, + "bind:", *Bind, + "pypi:", *PyPIURL, + "json:", *JSONURL, + "hr: /", ) - if *tlsCert == "" { + if *TLSCert == "" { err = server.Serve(ln) } else { - err = server.ServeTLS(ln, *tlsCert, *tlsKey) + err = server.ServeTLS(ln, *TLSCert, *TLSKey) } if err != http.ErrServerClosed { log.Fatal(err)