code.gitea.io/gitea@v1.21.7/routers/api/packages/rubygems/rubygems.go (about) 1 // Copyright 2021 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package rubygems 5 6 import ( 7 "compress/gzip" 8 "compress/zlib" 9 "errors" 10 "fmt" 11 "io" 12 "net/http" 13 "strings" 14 15 packages_model "code.gitea.io/gitea/models/packages" 16 "code.gitea.io/gitea/modules/context" 17 packages_module "code.gitea.io/gitea/modules/packages" 18 rubygems_module "code.gitea.io/gitea/modules/packages/rubygems" 19 "code.gitea.io/gitea/modules/util" 20 "code.gitea.io/gitea/routers/api/packages/helper" 21 packages_service "code.gitea.io/gitea/services/packages" 22 ) 23 24 func apiError(ctx *context.Context, status int, obj any) { 25 helper.LogAndProcessError(ctx, status, obj, func(message string) { 26 ctx.PlainText(status, message) 27 }) 28 } 29 30 // EnumeratePackages serves the package list 31 func EnumeratePackages(ctx *context.Context) { 32 packages, err := packages_model.GetVersionsByPackageType(ctx, ctx.Package.Owner.ID, packages_model.TypeRubyGems) 33 if err != nil { 34 apiError(ctx, http.StatusInternalServerError, err) 35 return 36 } 37 38 enumeratePackages(ctx, "specs.4.8", packages) 39 } 40 41 // EnumeratePackagesLatest serves the list of the latest version of every package 42 func EnumeratePackagesLatest(ctx *context.Context) { 43 pvs, _, err := packages_model.SearchLatestVersions(ctx, &packages_model.PackageSearchOptions{ 44 OwnerID: ctx.Package.Owner.ID, 45 Type: packages_model.TypeRubyGems, 46 IsInternal: util.OptionalBoolFalse, 47 }) 48 if err != nil { 49 apiError(ctx, http.StatusInternalServerError, err) 50 return 51 } 52 53 enumeratePackages(ctx, "latest_specs.4.8", pvs) 54 } 55 56 // EnumeratePackagesPreRelease is not supported and serves an empty list 57 func EnumeratePackagesPreRelease(ctx *context.Context) { 58 enumeratePackages(ctx, "prerelease_specs.4.8", []*packages_model.PackageVersion{}) 59 } 60 61 func enumeratePackages(ctx *context.Context, filename string, pvs []*packages_model.PackageVersion) { 62 pds, err := packages_model.GetPackageDescriptors(ctx, pvs) 63 if err != nil { 64 apiError(ctx, http.StatusInternalServerError, err) 65 return 66 } 67 68 specs := make([]any, 0, len(pds)) 69 for _, p := range pds { 70 specs = append(specs, []any{ 71 p.Package.Name, 72 &rubygems_module.RubyUserMarshal{ 73 Name: "Gem::Version", 74 Value: []string{p.Version.Version}, 75 }, 76 p.Metadata.(*rubygems_module.Metadata).Platform, 77 }) 78 } 79 80 ctx.SetServeHeaders(&context.ServeHeaderOptions{ 81 Filename: filename + ".gz", 82 }) 83 84 zw := gzip.NewWriter(ctx.Resp) 85 defer zw.Close() 86 87 zw.Name = filename 88 89 if err := rubygems_module.NewMarshalEncoder(zw).Encode(specs); err != nil { 90 ctx.ServerError("Download file failed", err) 91 } 92 } 93 94 // ServePackageSpecification serves the compressed Gemspec file of a package 95 func ServePackageSpecification(ctx *context.Context) { 96 filename := ctx.Params("filename") 97 98 if !strings.HasSuffix(filename, ".gemspec.rz") { 99 apiError(ctx, http.StatusNotImplemented, nil) 100 return 101 } 102 103 pvs, err := getVersionsByFilename(ctx, filename[:len(filename)-10]+"gem") 104 if err != nil { 105 apiError(ctx, http.StatusInternalServerError, err) 106 return 107 } 108 109 if len(pvs) != 1 { 110 apiError(ctx, http.StatusNotFound, nil) 111 return 112 } 113 114 pd, err := packages_model.GetPackageDescriptor(ctx, pvs[0]) 115 if err != nil { 116 apiError(ctx, http.StatusInternalServerError, err) 117 return 118 } 119 120 ctx.SetServeHeaders(&context.ServeHeaderOptions{ 121 Filename: filename, 122 }) 123 124 zw := zlib.NewWriter(ctx.Resp) 125 defer zw.Close() 126 127 metadata := pd.Metadata.(*rubygems_module.Metadata) 128 129 // create a Ruby Gem::Specification object 130 spec := &rubygems_module.RubyUserDef{ 131 Name: "Gem::Specification", 132 Value: []any{ 133 "3.2.3", // @rubygems_version 134 4, // @specification_version, 135 pd.Package.Name, 136 &rubygems_module.RubyUserMarshal{ 137 Name: "Gem::Version", 138 Value: []string{pd.Version.Version}, 139 }, 140 nil, // date 141 metadata.Summary, // @summary 142 nil, // @required_ruby_version 143 nil, // @required_rubygems_version 144 metadata.Platform, // @original_platform 145 []any{}, // @dependencies 146 nil, // rubyforge_project 147 "", // @email 148 metadata.Authors, 149 metadata.Description, 150 metadata.ProjectURL, 151 true, // has_rdoc 152 metadata.Platform, // @new_platform 153 nil, 154 metadata.Licenses, 155 }, 156 } 157 158 if err := rubygems_module.NewMarshalEncoder(zw).Encode(spec); err != nil { 159 ctx.ServerError("Download file failed", err) 160 } 161 } 162 163 // DownloadPackageFile serves the content of a package 164 func DownloadPackageFile(ctx *context.Context) { 165 filename := ctx.Params("filename") 166 167 pvs, err := getVersionsByFilename(ctx, filename) 168 if err != nil { 169 apiError(ctx, http.StatusInternalServerError, err) 170 return 171 } 172 173 if len(pvs) != 1 { 174 apiError(ctx, http.StatusNotFound, nil) 175 return 176 } 177 178 s, u, pf, err := packages_service.GetFileStreamByPackageVersion( 179 ctx, 180 pvs[0], 181 &packages_service.PackageFileInfo{ 182 Filename: filename, 183 }, 184 ) 185 if err != nil { 186 if err == packages_model.ErrPackageFileNotExist { 187 apiError(ctx, http.StatusNotFound, err) 188 return 189 } 190 apiError(ctx, http.StatusInternalServerError, err) 191 return 192 } 193 194 helper.ServePackageFile(ctx, s, u, pf) 195 } 196 197 // UploadPackageFile adds a file to the package. If the package does not exist, it gets created. 198 func UploadPackageFile(ctx *context.Context) { 199 upload, close, err := ctx.UploadStream() 200 if err != nil { 201 apiError(ctx, http.StatusBadRequest, err) 202 return 203 } 204 if close { 205 defer upload.Close() 206 } 207 208 buf, err := packages_module.CreateHashedBufferFromReader(upload) 209 if err != nil { 210 apiError(ctx, http.StatusInternalServerError, err) 211 return 212 } 213 defer buf.Close() 214 215 rp, err := rubygems_module.ParsePackageMetaData(buf) 216 if err != nil { 217 if errors.Is(err, util.ErrInvalidArgument) { 218 apiError(ctx, http.StatusBadRequest, err) 219 } else { 220 apiError(ctx, http.StatusInternalServerError, err) 221 } 222 return 223 } 224 if _, err := buf.Seek(0, io.SeekStart); err != nil { 225 apiError(ctx, http.StatusInternalServerError, err) 226 return 227 } 228 229 var filename string 230 if rp.Metadata.Platform == "" || rp.Metadata.Platform == "ruby" { 231 filename = strings.ToLower(fmt.Sprintf("%s-%s.gem", rp.Name, rp.Version)) 232 } else { 233 filename = strings.ToLower(fmt.Sprintf("%s-%s-%s.gem", rp.Name, rp.Version, rp.Metadata.Platform)) 234 } 235 236 _, _, err = packages_service.CreatePackageAndAddFile( 237 ctx, 238 &packages_service.PackageCreationInfo{ 239 PackageInfo: packages_service.PackageInfo{ 240 Owner: ctx.Package.Owner, 241 PackageType: packages_model.TypeRubyGems, 242 Name: rp.Name, 243 Version: rp.Version, 244 }, 245 SemverCompatible: true, 246 Creator: ctx.Doer, 247 Metadata: rp.Metadata, 248 }, 249 &packages_service.PackageFileCreationInfo{ 250 PackageFileInfo: packages_service.PackageFileInfo{ 251 Filename: filename, 252 }, 253 Creator: ctx.Doer, 254 Data: buf, 255 IsLead: true, 256 }, 257 ) 258 if err != nil { 259 switch err { 260 case packages_model.ErrDuplicatePackageVersion: 261 apiError(ctx, http.StatusBadRequest, err) 262 case packages_service.ErrQuotaTotalCount, packages_service.ErrQuotaTypeSize, packages_service.ErrQuotaTotalSize: 263 apiError(ctx, http.StatusForbidden, err) 264 default: 265 apiError(ctx, http.StatusInternalServerError, err) 266 } 267 return 268 } 269 270 ctx.Status(http.StatusCreated) 271 } 272 273 // DeletePackage deletes a package 274 func DeletePackage(ctx *context.Context) { 275 // Go populates the form only for POST, PUT and PATCH requests 276 if err := ctx.Req.ParseMultipartForm(32 << 20); err != nil { 277 apiError(ctx, http.StatusInternalServerError, err) 278 return 279 } 280 packageName := ctx.FormString("gem_name") 281 packageVersion := ctx.FormString("version") 282 283 err := packages_service.RemovePackageVersionByNameAndVersion( 284 ctx, 285 ctx.Doer, 286 &packages_service.PackageInfo{ 287 Owner: ctx.Package.Owner, 288 PackageType: packages_model.TypeRubyGems, 289 Name: packageName, 290 Version: packageVersion, 291 }, 292 ) 293 if err != nil { 294 if err == packages_model.ErrPackageNotExist { 295 apiError(ctx, http.StatusNotFound, err) 296 return 297 } 298 apiError(ctx, http.StatusInternalServerError, err) 299 } 300 } 301 302 func getVersionsByFilename(ctx *context.Context, filename string) ([]*packages_model.PackageVersion, error) { 303 pvs, _, err := packages_model.SearchVersions(ctx, &packages_model.PackageSearchOptions{ 304 OwnerID: ctx.Package.Owner.ID, 305 Type: packages_model.TypeRubyGems, 306 HasFileWithName: filename, 307 IsInternal: util.OptionalBoolFalse, 308 }) 309 return pvs, err 310 }