code.gitea.io/gitea@v1.21.7/routers/api/packages/debian/debian.go (about) 1 // Copyright 2023 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package debian 5 6 import ( 7 stdctx "context" 8 "errors" 9 "fmt" 10 "io" 11 "net/http" 12 "strings" 13 14 "code.gitea.io/gitea/models/db" 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 debian_module "code.gitea.io/gitea/modules/packages/debian" 19 "code.gitea.io/gitea/modules/util" 20 "code.gitea.io/gitea/routers/api/packages/helper" 21 notify_service "code.gitea.io/gitea/services/notify" 22 packages_service "code.gitea.io/gitea/services/packages" 23 debian_service "code.gitea.io/gitea/services/packages/debian" 24 ) 25 26 func apiError(ctx *context.Context, status int, obj any) { 27 helper.LogAndProcessError(ctx, status, obj, func(message string) { 28 ctx.PlainText(status, message) 29 }) 30 } 31 32 func GetRepositoryKey(ctx *context.Context) { 33 _, pub, err := debian_service.GetOrCreateKeyPair(ctx, ctx.Package.Owner.ID) 34 if err != nil { 35 apiError(ctx, http.StatusInternalServerError, err) 36 return 37 } 38 39 ctx.ServeContent(strings.NewReader(pub), &context.ServeHeaderOptions{ 40 ContentType: "application/pgp-keys", 41 Filename: "repository.key", 42 }) 43 } 44 45 // https://wiki.debian.org/DebianRepository/Format#A.22Release.22_files 46 // https://wiki.debian.org/DebianRepository/Format#A.22Packages.22_Indices 47 func GetRepositoryFile(ctx *context.Context) { 48 pv, err := debian_service.GetOrCreateRepositoryVersion(ctx, ctx.Package.Owner.ID) 49 if err != nil { 50 apiError(ctx, http.StatusInternalServerError, err) 51 return 52 } 53 54 key := ctx.Params("distribution") 55 56 component := ctx.Params("component") 57 architecture := strings.TrimPrefix(ctx.Params("architecture"), "binary-") 58 if component != "" && architecture != "" { 59 key += "|" + component + "|" + architecture 60 } 61 62 s, u, pf, err := packages_service.GetFileStreamByPackageVersion( 63 ctx, 64 pv, 65 &packages_service.PackageFileInfo{ 66 Filename: ctx.Params("filename"), 67 CompositeKey: key, 68 }, 69 ) 70 if err != nil { 71 if err == packages_model.ErrPackageNotExist || err == packages_model.ErrPackageFileNotExist { 72 apiError(ctx, http.StatusNotFound, err) 73 } else { 74 apiError(ctx, http.StatusInternalServerError, err) 75 } 76 return 77 } 78 79 helper.ServePackageFile(ctx, s, u, pf) 80 } 81 82 // https://wiki.debian.org/DebianRepository/Format#indices_acquisition_via_hashsums_.28by-hash.29 83 func GetRepositoryFileByHash(ctx *context.Context) { 84 pv, err := debian_service.GetOrCreateRepositoryVersion(ctx, ctx.Package.Owner.ID) 85 if err != nil { 86 apiError(ctx, http.StatusInternalServerError, err) 87 return 88 } 89 90 algorithm := strings.ToLower(ctx.Params("algorithm")) 91 if algorithm == "md5sum" { 92 algorithm = "md5" 93 } 94 95 pfs, _, err := packages_model.SearchFiles(ctx, &packages_model.PackageFileSearchOptions{ 96 VersionID: pv.ID, 97 Hash: strings.ToLower(ctx.Params("hash")), 98 HashAlgorithm: algorithm, 99 }) 100 if err != nil { 101 apiError(ctx, http.StatusInternalServerError, err) 102 return 103 } 104 if len(pfs) != 1 { 105 apiError(ctx, http.StatusNotFound, nil) 106 return 107 } 108 109 s, u, pf, err := packages_service.GetPackageFileStream(ctx, pfs[0]) 110 if err != nil { 111 if errors.Is(err, util.ErrNotExist) { 112 apiError(ctx, http.StatusNotFound, err) 113 } else { 114 apiError(ctx, http.StatusInternalServerError, err) 115 } 116 return 117 } 118 119 helper.ServePackageFile(ctx, s, u, pf) 120 } 121 122 func UploadPackageFile(ctx *context.Context) { 123 distribution := strings.TrimSpace(ctx.Params("distribution")) 124 component := strings.TrimSpace(ctx.Params("component")) 125 if distribution == "" || component == "" { 126 apiError(ctx, http.StatusBadRequest, "invalid distribution or component") 127 return 128 } 129 130 upload, close, err := ctx.UploadStream() 131 if err != nil { 132 apiError(ctx, http.StatusInternalServerError, err) 133 return 134 } 135 if close { 136 defer upload.Close() 137 } 138 139 buf, err := packages_module.CreateHashedBufferFromReader(upload) 140 if err != nil { 141 apiError(ctx, http.StatusInternalServerError, err) 142 return 143 } 144 defer buf.Close() 145 146 pck, err := debian_module.ParsePackage(buf) 147 if err != nil { 148 if errors.Is(err, util.ErrInvalidArgument) { 149 apiError(ctx, http.StatusBadRequest, err) 150 } else { 151 apiError(ctx, http.StatusInternalServerError, err) 152 } 153 return 154 } 155 156 if _, err := buf.Seek(0, io.SeekStart); err != nil { 157 apiError(ctx, http.StatusInternalServerError, err) 158 return 159 } 160 161 _, _, err = packages_service.CreatePackageOrAddFileToExisting( 162 ctx, 163 &packages_service.PackageCreationInfo{ 164 PackageInfo: packages_service.PackageInfo{ 165 Owner: ctx.Package.Owner, 166 PackageType: packages_model.TypeDebian, 167 Name: pck.Name, 168 Version: pck.Version, 169 }, 170 Creator: ctx.Doer, 171 Metadata: pck.Metadata, 172 }, 173 &packages_service.PackageFileCreationInfo{ 174 PackageFileInfo: packages_service.PackageFileInfo{ 175 Filename: fmt.Sprintf("%s_%s_%s.deb", pck.Name, pck.Version, pck.Architecture), 176 CompositeKey: fmt.Sprintf("%s|%s", distribution, component), 177 }, 178 Creator: ctx.Doer, 179 Data: buf, 180 IsLead: true, 181 Properties: map[string]string{ 182 debian_module.PropertyDistribution: distribution, 183 debian_module.PropertyComponent: component, 184 debian_module.PropertyArchitecture: pck.Architecture, 185 debian_module.PropertyControl: pck.Control, 186 }, 187 }, 188 ) 189 if err != nil { 190 switch err { 191 case packages_model.ErrDuplicatePackageVersion, packages_model.ErrDuplicatePackageFile: 192 apiError(ctx, http.StatusBadRequest, err) 193 case packages_service.ErrQuotaTotalCount, packages_service.ErrQuotaTypeSize, packages_service.ErrQuotaTotalSize: 194 apiError(ctx, http.StatusForbidden, err) 195 default: 196 apiError(ctx, http.StatusInternalServerError, err) 197 } 198 return 199 } 200 201 if err := debian_service.BuildSpecificRepositoryFiles(ctx, ctx.Package.Owner.ID, distribution, component, pck.Architecture); err != nil { 202 apiError(ctx, http.StatusInternalServerError, err) 203 return 204 } 205 206 ctx.Status(http.StatusCreated) 207 } 208 209 func DownloadPackageFile(ctx *context.Context) { 210 name := ctx.Params("name") 211 version := ctx.Params("version") 212 213 s, u, pf, err := packages_service.GetFileStreamByPackageNameAndVersion( 214 ctx, 215 &packages_service.PackageInfo{ 216 Owner: ctx.Package.Owner, 217 PackageType: packages_model.TypeDebian, 218 Name: name, 219 Version: version, 220 }, 221 &packages_service.PackageFileInfo{ 222 Filename: fmt.Sprintf("%s_%s_%s.deb", name, version, ctx.Params("architecture")), 223 CompositeKey: fmt.Sprintf("%s|%s", ctx.Params("distribution"), ctx.Params("component")), 224 }, 225 ) 226 if err != nil { 227 if errors.Is(err, util.ErrNotExist) { 228 apiError(ctx, http.StatusNotFound, err) 229 } else { 230 apiError(ctx, http.StatusInternalServerError, err) 231 } 232 return 233 } 234 235 helper.ServePackageFile(ctx, s, u, pf, &context.ServeHeaderOptions{ 236 ContentType: "application/vnd.debian.binary-package", 237 Filename: pf.Name, 238 LastModified: pf.CreatedUnix.AsLocalTime(), 239 }) 240 } 241 242 func DeletePackageFile(ctx *context.Context) { 243 distribution := ctx.Params("distribution") 244 component := ctx.Params("component") 245 name := ctx.Params("name") 246 version := ctx.Params("version") 247 architecture := ctx.Params("architecture") 248 249 owner := ctx.Package.Owner 250 251 var pd *packages_model.PackageDescriptor 252 253 err := db.WithTx(ctx, func(ctx stdctx.Context) error { 254 pv, err := packages_model.GetVersionByNameAndVersion(ctx, owner.ID, packages_model.TypeDebian, name, version) 255 if err != nil { 256 return err 257 } 258 259 pf, err := packages_model.GetFileForVersionByName( 260 ctx, 261 pv.ID, 262 fmt.Sprintf("%s_%s_%s.deb", name, version, architecture), 263 fmt.Sprintf("%s|%s", distribution, component), 264 ) 265 if err != nil { 266 return err 267 } 268 269 if err := packages_service.DeletePackageFile(ctx, pf); err != nil { 270 return err 271 } 272 273 has, err := packages_model.HasVersionFileReferences(ctx, pv.ID) 274 if err != nil { 275 return err 276 } 277 if !has { 278 pd, err = packages_model.GetPackageDescriptor(ctx, pv) 279 if err != nil { 280 return err 281 } 282 283 if err := packages_service.DeletePackageVersionAndReferences(ctx, pv); err != nil { 284 return err 285 } 286 } 287 288 return nil 289 }) 290 if err != nil { 291 if errors.Is(err, util.ErrNotExist) { 292 apiError(ctx, http.StatusNotFound, err) 293 } else { 294 apiError(ctx, http.StatusInternalServerError, err) 295 } 296 return 297 } 298 299 if pd != nil { 300 notify_service.PackageDelete(ctx, ctx.Doer, pd) 301 } 302 303 if err := debian_service.BuildSpecificRepositoryFiles(ctx, ctx.Package.Owner.ID, distribution, component, architecture); err != nil { 304 apiError(ctx, http.StatusInternalServerError, err) 305 return 306 } 307 308 ctx.Status(http.StatusNoContent) 309 }