// GoCheese -- Python private package repository and caching proxy // Copyright (C) 2019-2024 Sergey Matveev // // 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 // the Free Software Foundation, version 3 of the License. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . package main import ( "bytes" "encoding/json" "log" "net/http" "os" "path/filepath" "strings" "go.cypherpunks.ru/recfile" ) func getMD(pkgName, version string) (*PkgMeta, []*PkgReleaseInfo, error) { serial, releases, err := listDir(pkgName, true) if err != nil { return nil, nil, err } md, err := os.ReadFile(filepath.Join(Root, pkgName, MDFile)) if err != nil { if !os.IsNotExist(err) { return nil, nil, err } } info := PkgInfo{Name: pkgName} if len(md) == 0 { info.Version = releases[len(releases)-1].Version } else { m, err := recfile.NewReader(bytes.NewReader(md)).NextMapWithSlice() if err != nil { return nil, nil, err } if v, ok := m[MDFieldToRecField[MDFieldVersion]]; ok { info.Version = v[0] } if v, ok := m[MDFieldToRecField[MDFieldSummary]]; ok { info.Summary = v[0] } if v, ok := m[MDFieldToRecField[MDFieldDescriptionContentType]]; ok { info.DescriptionContentType = v[0] } if v, ok := m[MDFieldToRecField[MDFieldKeywords]]; ok { info.Keywords = v[0] } if v, ok := m[MDFieldToRecField[MDFieldHomePage]]; ok { info.HomePage = v[0] } if v, ok := m[MDFieldToRecField[MDFieldAuthor]]; ok { info.Author = v[0] } if v, ok := m[MDFieldToRecField[MDFieldAuthorEmail]]; ok { info.AuthorEmail = v[0] } if v, ok := m[MDFieldToRecField[MDFieldMaintainer]]; ok { info.Maintainer = v[0] } if v, ok := m[MDFieldToRecField[MDFieldMaintainerEmail]]; ok { info.MaintainerEmail = v[0] } if v, ok := m[MDFieldToRecField[MDFieldLicense]]; ok { info.License = v[0] } if v, ok := m[MDFieldToRecField[MDFieldRequiresPython]]; ok { info.RequiresPython = v[0] } if v, ok := m[MDFieldToRecField[MDFieldDescription]]; ok { info.Description = v[0] } info.Classifier = m[MDFieldToRecField[MDFieldClassifier]] info.Platform = m[MDFieldToRecField[MDFieldPlatform]] info.SupportedPlatform = m[MDFieldToRecField[MDFieldSupportedPlatform]] info.RequiresDist = m[MDFieldToRecField[MDFieldRequiresDist]] info.RequiresExternal = m[MDFieldToRecField[MDFieldRequiresExternal]] info.ProjectURL = m[MDFieldToRecField[MDFieldProjectURL]] info.ProvidesExtra = m[MDFieldToRecField[MDFieldProvidesExtra]] } meta := PkgMeta{ Info: info, LastSerial: serial, Releases: make(map[string][]*PkgReleaseInfo), } var lastVersion string for _, release := range releases { meta.Releases[release.Version] = append( meta.Releases[release.Version], release, ) lastVersion = release.Version } if version != "" { lastVersion = version } meta.URLs = meta.Releases[lastVersion] return &meta, releases, nil } // https://warehouse.pypa.io/api-reference/json.html func serveJSON(w http.ResponseWriter, r *http.Request) { path := strings.TrimPrefix(r.URL.Path, *JSONURLPath) parts := strings.Split(strings.TrimSuffix(path, "/"), "/") if len(parts) < 2 || parts[len(parts)-1] != "json" { http.Error(w, "invalid JSON API action", http.StatusBadRequest) return } var version string if len(parts) == 3 { version = parts[1] } pkgName := parts[0] meta, _, err := getMD(pkgName, version) if err != nil { if os.IsNotExist(err) { http.NotFound(w, r) } else { log.Println("error", r.RemoteAddr, "json", pkgName, err) http.Error(w, err.Error(), http.StatusInternalServerError) } return } w.Header().Set("Content-Type", "application/json") buf, err := json.Marshal(&meta) if err != nil { log.Println("error", r.RemoteAddr, "json", pkgName, err) http.Error(w, err.Error(), http.StatusInternalServerError) return } w.Write(buf) }