code.gitea.io/gitea@v1.21.7/routers/api/packages/pub/pub.go (about) 1 // Copyright 2022 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package pub 5 6 import ( 7 "errors" 8 "fmt" 9 "io" 10 "net/http" 11 "net/url" 12 "sort" 13 "strings" 14 "time" 15 16 packages_model "code.gitea.io/gitea/models/packages" 17 "code.gitea.io/gitea/modules/context" 18 "code.gitea.io/gitea/modules/json" 19 "code.gitea.io/gitea/modules/log" 20 packages_module "code.gitea.io/gitea/modules/packages" 21 pub_module "code.gitea.io/gitea/modules/packages/pub" 22 "code.gitea.io/gitea/modules/setting" 23 "code.gitea.io/gitea/modules/util" 24 "code.gitea.io/gitea/routers/api/packages/helper" 25 packages_service "code.gitea.io/gitea/services/packages" 26 ) 27 28 func jsonResponse(ctx *context.Context, status int, obj any) { 29 resp := ctx.Resp 30 resp.Header().Set("Content-Type", "application/vnd.pub.v2+json") 31 resp.WriteHeader(status) 32 if err := json.NewEncoder(resp).Encode(obj); err != nil { 33 log.Error("JSON encode: %v", err) 34 } 35 } 36 37 func apiError(ctx *context.Context, status int, obj any) { 38 type Error struct { 39 Code string `json:"code"` 40 Message string `json:"message"` 41 } 42 type ErrorWrapper struct { 43 Error Error `json:"error"` 44 } 45 46 helper.LogAndProcessError(ctx, status, obj, func(message string) { 47 jsonResponse(ctx, status, ErrorWrapper{ 48 Error: Error{ 49 Code: http.StatusText(status), 50 Message: message, 51 }, 52 }) 53 }) 54 } 55 56 type packageVersions struct { 57 Name string `json:"name"` 58 Latest *versionMetadata `json:"latest"` 59 Versions []*versionMetadata `json:"versions"` 60 } 61 62 type versionMetadata struct { 63 Version string `json:"version"` 64 ArchiveURL string `json:"archive_url"` 65 Published time.Time `json:"published"` 66 Pubspec any `json:"pubspec,omitempty"` 67 } 68 69 func packageDescriptorToMetadata(baseURL string, pd *packages_model.PackageDescriptor) *versionMetadata { 70 return &versionMetadata{ 71 Version: pd.Version.Version, 72 ArchiveURL: fmt.Sprintf("%s/files/%s.tar.gz", baseURL, url.PathEscape(pd.Version.Version)), 73 Published: pd.Version.CreatedUnix.AsLocalTime(), 74 Pubspec: pd.Metadata.(*pub_module.Metadata).Pubspec, 75 } 76 } 77 78 func baseURL(ctx *context.Context) string { 79 return setting.AppURL + "api/packages/" + ctx.Package.Owner.Name + "/pub/api/packages" 80 } 81 82 // https://github.com/dart-lang/pub/blob/master/doc/repository-spec-v2.md#list-all-versions-of-a-package 83 func EnumeratePackageVersions(ctx *context.Context) { 84 packageName := ctx.Params("id") 85 86 pvs, err := packages_model.GetVersionsByPackageName(ctx, ctx.Package.Owner.ID, packages_model.TypePub, packageName) 87 if err != nil { 88 apiError(ctx, http.StatusInternalServerError, err) 89 return 90 } 91 if len(pvs) == 0 { 92 apiError(ctx, http.StatusNotFound, err) 93 return 94 } 95 96 pds, err := packages_model.GetPackageDescriptors(ctx, pvs) 97 if err != nil { 98 apiError(ctx, http.StatusInternalServerError, err) 99 return 100 } 101 102 sort.Slice(pds, func(i, j int) bool { 103 return pds[i].SemVer.LessThan(pds[j].SemVer) 104 }) 105 106 baseURL := fmt.Sprintf("%s/%s", baseURL(ctx), url.PathEscape(pds[0].Package.Name)) 107 108 versions := make([]*versionMetadata, 0, len(pds)) 109 for _, pd := range pds { 110 versions = append(versions, packageDescriptorToMetadata(baseURL, pd)) 111 } 112 113 jsonResponse(ctx, http.StatusOK, &packageVersions{ 114 Name: pds[0].Package.Name, 115 Latest: packageDescriptorToMetadata(baseURL, pds[0]), 116 Versions: versions, 117 }) 118 } 119 120 // https://github.com/dart-lang/pub/blob/master/doc/repository-spec-v2.md#deprecated-inspect-a-specific-version-of-a-package 121 func PackageVersionMetadata(ctx *context.Context) { 122 packageName := ctx.Params("id") 123 packageVersion := ctx.Params("version") 124 125 pv, err := packages_model.GetVersionByNameAndVersion(ctx, ctx.Package.Owner.ID, packages_model.TypePub, packageName, packageVersion) 126 if err != nil { 127 if err == packages_model.ErrPackageNotExist { 128 apiError(ctx, http.StatusNotFound, err) 129 return 130 } 131 apiError(ctx, http.StatusInternalServerError, err) 132 return 133 } 134 135 pd, err := packages_model.GetPackageDescriptor(ctx, pv) 136 if err != nil { 137 apiError(ctx, http.StatusInternalServerError, err) 138 return 139 } 140 141 jsonResponse(ctx, http.StatusOK, packageDescriptorToMetadata( 142 fmt.Sprintf("%s/%s", baseURL(ctx), url.PathEscape(pd.Package.Name)), 143 pd, 144 )) 145 } 146 147 // https://github.com/dart-lang/pub/blob/master/doc/repository-spec-v2.md#publishing-packages 148 func RequestUpload(ctx *context.Context) { 149 type UploadRequest struct { 150 URL string `json:"url"` 151 Fields map[string]string `json:"fields"` 152 } 153 154 jsonResponse(ctx, http.StatusOK, UploadRequest{ 155 URL: baseURL(ctx) + "/versions/new/upload", 156 Fields: make(map[string]string), 157 }) 158 } 159 160 // https://github.com/dart-lang/pub/blob/master/doc/repository-spec-v2.md#publishing-packages 161 func UploadPackageFile(ctx *context.Context) { 162 file, _, err := ctx.Req.FormFile("file") 163 if err != nil { 164 apiError(ctx, http.StatusBadRequest, err) 165 return 166 } 167 defer file.Close() 168 169 buf, err := packages_module.CreateHashedBufferFromReader(file) 170 if err != nil { 171 apiError(ctx, http.StatusInternalServerError, err) 172 return 173 } 174 defer buf.Close() 175 176 pck, err := pub_module.ParsePackage(buf) 177 if err != nil { 178 if errors.Is(err, util.ErrInvalidArgument) { 179 apiError(ctx, http.StatusBadRequest, err) 180 } else { 181 apiError(ctx, http.StatusInternalServerError, err) 182 } 183 return 184 } 185 186 if _, err := buf.Seek(0, io.SeekStart); err != nil { 187 apiError(ctx, http.StatusInternalServerError, err) 188 return 189 } 190 191 _, _, err = packages_service.CreatePackageAndAddFile( 192 ctx, 193 &packages_service.PackageCreationInfo{ 194 PackageInfo: packages_service.PackageInfo{ 195 Owner: ctx.Package.Owner, 196 PackageType: packages_model.TypePub, 197 Name: pck.Name, 198 Version: pck.Version, 199 }, 200 SemverCompatible: true, 201 Creator: ctx.Doer, 202 Metadata: pck.Metadata, 203 }, 204 &packages_service.PackageFileCreationInfo{ 205 PackageFileInfo: packages_service.PackageFileInfo{ 206 Filename: strings.ToLower(pck.Version + ".tar.gz"), 207 }, 208 Creator: ctx.Doer, 209 Data: buf, 210 IsLead: true, 211 }, 212 ) 213 if err != nil { 214 switch err { 215 case packages_model.ErrDuplicatePackageVersion: 216 apiError(ctx, http.StatusBadRequest, err) 217 case packages_service.ErrQuotaTotalCount, packages_service.ErrQuotaTypeSize, packages_service.ErrQuotaTotalSize: 218 apiError(ctx, http.StatusForbidden, err) 219 default: 220 apiError(ctx, http.StatusInternalServerError, err) 221 } 222 return 223 } 224 225 ctx.Resp.Header().Set("Location", fmt.Sprintf("%s/versions/new/finalize/%s/%s", baseURL(ctx), url.PathEscape(pck.Name), url.PathEscape(pck.Version))) 226 ctx.Status(http.StatusNoContent) 227 } 228 229 // https://github.com/dart-lang/pub/blob/master/doc/repository-spec-v2.md#publishing-packages 230 func FinalizePackage(ctx *context.Context) { 231 packageName := ctx.Params("id") 232 packageVersion := ctx.Params("version") 233 234 _, err := packages_model.GetVersionByNameAndVersion(ctx, ctx.Package.Owner.ID, packages_model.TypePub, packageName, packageVersion) 235 if err != nil { 236 if err == packages_model.ErrPackageNotExist { 237 apiError(ctx, http.StatusNotFound, err) 238 return 239 } 240 apiError(ctx, http.StatusInternalServerError, err) 241 return 242 } 243 244 type Success struct { 245 Message string `json:"message"` 246 } 247 type SuccessWrapper struct { 248 Success Success `json:"success"` 249 } 250 251 jsonResponse(ctx, http.StatusOK, SuccessWrapper{Success{}}) 252 } 253 254 // https://github.com/dart-lang/pub/blob/master/doc/repository-spec-v2.md#deprecated-download-a-specific-version-of-a-package 255 func DownloadPackageFile(ctx *context.Context) { 256 packageName := ctx.Params("id") 257 packageVersion := strings.TrimSuffix(ctx.Params("version"), ".tar.gz") 258 259 pv, err := packages_model.GetVersionByNameAndVersion(ctx, ctx.Package.Owner.ID, packages_model.TypePub, packageName, packageVersion) 260 if err != nil { 261 if err == packages_model.ErrPackageNotExist { 262 apiError(ctx, http.StatusNotFound, err) 263 return 264 } 265 apiError(ctx, http.StatusInternalServerError, err) 266 return 267 } 268 269 pd, err := packages_model.GetPackageDescriptor(ctx, pv) 270 if err != nil { 271 apiError(ctx, http.StatusInternalServerError, err) 272 return 273 } 274 275 pf := pd.Files[0].File 276 277 s, u, _, err := packages_service.GetPackageFileStream(ctx, pf) 278 if err != nil { 279 apiError(ctx, http.StatusInternalServerError, err) 280 return 281 } 282 283 helper.ServePackageFile(ctx, s, u, pf) 284 }