]> Cypherpunks.ru repositories - gocheese.git/blob - json.go
More convenient trusted-host
[gocheese.git] / json.go
1 // GoCheese -- Python private package repository and caching proxy
2 // Copyright (C) 2019-2024 Sergey Matveev <stargrave@stargrave.org>
3 //
4 // This program is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, version 3 of the License.
7 //
8 // This program is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 // GNU General Public License for more details.
12 //
13 // You should have received a copy of the GNU General Public License
14 // along with this program.  If not, see <http://www.gnu.org/licenses/>.
15
16 package main
17
18 import (
19         "bytes"
20         "encoding/json"
21         "log"
22         "net/http"
23         "os"
24         "path/filepath"
25         "strings"
26
27         "go.cypherpunks.ru/recfile"
28 )
29
30 func getMD(pkgName, version string) (*PkgMeta, []*PkgReleaseInfo, error) {
31         serial, releases, err := listDir(pkgName, true)
32         if err != nil {
33                 return nil, nil, err
34         }
35         md, err := os.ReadFile(filepath.Join(Root, pkgName, MDFile))
36         if err != nil {
37                 if !os.IsNotExist(err) {
38                         return nil, nil, err
39                 }
40         }
41         info := PkgInfo{Name: pkgName}
42         if len(md) == 0 {
43                 info.Version = releases[len(releases)-1].Version
44         } else {
45                 m, err := recfile.NewReader(bytes.NewReader(md)).NextMapWithSlice()
46                 if err != nil {
47                         return nil, nil, err
48                 }
49                 if v, ok := m[MDFieldToRecField[MDFieldVersion]]; ok {
50                         info.Version = v[0]
51                 }
52                 if v, ok := m[MDFieldToRecField[MDFieldSummary]]; ok {
53                         info.Summary = v[0]
54                 }
55                 if v, ok := m[MDFieldToRecField[MDFieldDescriptionContentType]]; ok {
56                         info.DescriptionContentType = v[0]
57                 }
58                 if v, ok := m[MDFieldToRecField[MDFieldKeywords]]; ok {
59                         info.Keywords = v[0]
60                 }
61                 if v, ok := m[MDFieldToRecField[MDFieldHomePage]]; ok {
62                         info.HomePage = v[0]
63                 }
64                 if v, ok := m[MDFieldToRecField[MDFieldAuthor]]; ok {
65                         info.Author = v[0]
66                 }
67                 if v, ok := m[MDFieldToRecField[MDFieldAuthorEmail]]; ok {
68                         info.AuthorEmail = v[0]
69                 }
70                 if v, ok := m[MDFieldToRecField[MDFieldMaintainer]]; ok {
71                         info.Maintainer = v[0]
72                 }
73                 if v, ok := m[MDFieldToRecField[MDFieldMaintainerEmail]]; ok {
74                         info.MaintainerEmail = v[0]
75                 }
76                 if v, ok := m[MDFieldToRecField[MDFieldLicense]]; ok {
77                         info.License = v[0]
78                 }
79                 if v, ok := m[MDFieldToRecField[MDFieldRequiresPython]]; ok {
80                         info.RequiresPython = v[0]
81                 }
82                 if v, ok := m[MDFieldToRecField[MDFieldDescription]]; ok {
83                         info.Description = v[0]
84                 }
85                 info.Classifier = m[MDFieldToRecField[MDFieldClassifier]]
86                 info.Platform = m[MDFieldToRecField[MDFieldPlatform]]
87                 info.SupportedPlatform = m[MDFieldToRecField[MDFieldSupportedPlatform]]
88                 info.RequiresDist = m[MDFieldToRecField[MDFieldRequiresDist]]
89                 info.RequiresExternal = m[MDFieldToRecField[MDFieldRequiresExternal]]
90                 info.ProjectURL = m[MDFieldToRecField[MDFieldProjectURL]]
91                 info.ProvidesExtra = m[MDFieldToRecField[MDFieldProvidesExtra]]
92         }
93         meta := PkgMeta{
94                 Info:       info,
95                 LastSerial: serial,
96                 Releases:   make(map[string][]*PkgReleaseInfo),
97         }
98         var lastVersion string
99         for _, release := range releases {
100                 meta.Releases[release.Version] = append(
101                         meta.Releases[release.Version], release,
102                 )
103                 lastVersion = release.Version
104         }
105         if version != "" {
106                 lastVersion = version
107         }
108         meta.URLs = meta.Releases[lastVersion]
109         return &meta, releases, nil
110 }
111
112 // https://warehouse.pypa.io/api-reference/json.html
113 func serveJSON(w http.ResponseWriter, r *http.Request) {
114         path := strings.TrimPrefix(r.URL.Path, *JSONURLPath)
115         parts := strings.Split(strings.TrimSuffix(path, "/"), "/")
116         if len(parts) < 2 || parts[len(parts)-1] != "json" {
117                 http.Error(w, "invalid JSON API action", http.StatusBadRequest)
118                 return
119         }
120         var version string
121         if len(parts) == 3 {
122                 version = parts[1]
123         }
124         pkgName := parts[0]
125
126         meta, _, err := getMD(pkgName, version)
127         if err != nil {
128                 if os.IsNotExist(err) {
129                         http.NotFound(w, r)
130                 } else {
131                         log.Println("error", r.RemoteAddr, "json", pkgName, err)
132                         http.Error(w, err.Error(), http.StatusInternalServerError)
133                 }
134                 return
135         }
136         w.Header().Set("Content-Type", "application/json")
137         buf, err := json.Marshal(&meta)
138         if err != nil {
139                 log.Println("error", r.RemoteAddr, "json", pkgName, err)
140                 http.Error(w, err.Error(), http.StatusInternalServerError)
141                 return
142         }
143         w.Write(buf)
144 }