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