code.gitea.io/gitea@v1.22.3/routers/api/packages/generic/generic.go (about) 1 // Copyright 2021 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package generic 5 6 import ( 7 "errors" 8 "net/http" 9 "regexp" 10 "strings" 11 "unicode" 12 13 packages_model "code.gitea.io/gitea/models/packages" 14 "code.gitea.io/gitea/modules/log" 15 packages_module "code.gitea.io/gitea/modules/packages" 16 "code.gitea.io/gitea/routers/api/packages/helper" 17 "code.gitea.io/gitea/services/context" 18 packages_service "code.gitea.io/gitea/services/packages" 19 ) 20 21 var ( 22 packageNameRegex = regexp.MustCompile(`\A[-_+.\w]+\z`) 23 filenameRegex = regexp.MustCompile(`\A[-_+=:;.()\[\]{}~!@#$%^& \w]+\z`) 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 // DownloadPackageFile serves the specific generic package. 33 func DownloadPackageFile(ctx *context.Context) { 34 s, u, pf, err := packages_service.GetFileStreamByPackageNameAndVersion( 35 ctx, 36 &packages_service.PackageInfo{ 37 Owner: ctx.Package.Owner, 38 PackageType: packages_model.TypeGeneric, 39 Name: ctx.Params("packagename"), 40 Version: ctx.Params("packageversion"), 41 }, 42 &packages_service.PackageFileInfo{ 43 Filename: ctx.Params("filename"), 44 }, 45 ) 46 if err != nil { 47 if err == packages_model.ErrPackageNotExist || err == packages_model.ErrPackageFileNotExist { 48 apiError(ctx, http.StatusNotFound, err) 49 return 50 } 51 apiError(ctx, http.StatusInternalServerError, err) 52 return 53 } 54 55 helper.ServePackageFile(ctx, s, u, pf) 56 } 57 58 func isValidPackageName(packageName string) bool { 59 if len(packageName) == 1 && !unicode.IsLetter(rune(packageName[0])) && !unicode.IsNumber(rune(packageName[0])) { 60 return false 61 } 62 return packageNameRegex.MatchString(packageName) && packageName != ".." 63 } 64 65 func isValidFileName(filename string) bool { 66 return filenameRegex.MatchString(filename) && 67 strings.TrimSpace(filename) == filename && 68 filename != "." && filename != ".." 69 } 70 71 // UploadPackage uploads the specific generic package. 72 // Duplicated packages get rejected. 73 func UploadPackage(ctx *context.Context) { 74 packageName := ctx.Params("packagename") 75 filename := ctx.Params("filename") 76 77 if !isValidPackageName(packageName) { 78 apiError(ctx, http.StatusBadRequest, errors.New("invalid package name")) 79 return 80 } 81 82 if !isValidFileName(filename) { 83 apiError(ctx, http.StatusBadRequest, errors.New("invalid filename")) 84 return 85 } 86 87 packageVersion := ctx.Params("packageversion") 88 if packageVersion != strings.TrimSpace(packageVersion) { 89 apiError(ctx, http.StatusBadRequest, errors.New("invalid package version")) 90 return 91 } 92 93 upload, needToClose, err := ctx.UploadStream() 94 if err != nil { 95 apiError(ctx, http.StatusInternalServerError, err) 96 return 97 } 98 if needToClose { 99 defer upload.Close() 100 } 101 102 buf, err := packages_module.CreateHashedBufferFromReader(upload) 103 if err != nil { 104 log.Error("Error creating hashed buffer: %v", err) 105 apiError(ctx, http.StatusInternalServerError, err) 106 return 107 } 108 defer buf.Close() 109 110 _, _, err = packages_service.CreatePackageOrAddFileToExisting( 111 ctx, 112 &packages_service.PackageCreationInfo{ 113 PackageInfo: packages_service.PackageInfo{ 114 Owner: ctx.Package.Owner, 115 PackageType: packages_model.TypeGeneric, 116 Name: packageName, 117 Version: packageVersion, 118 }, 119 Creator: ctx.Doer, 120 }, 121 &packages_service.PackageFileCreationInfo{ 122 PackageFileInfo: packages_service.PackageFileInfo{ 123 Filename: filename, 124 }, 125 Creator: ctx.Doer, 126 Data: buf, 127 IsLead: true, 128 }, 129 ) 130 if err != nil { 131 switch err { 132 case packages_model.ErrDuplicatePackageFile: 133 apiError(ctx, http.StatusConflict, err) 134 case packages_service.ErrQuotaTotalCount, packages_service.ErrQuotaTypeSize, packages_service.ErrQuotaTotalSize: 135 apiError(ctx, http.StatusForbidden, err) 136 default: 137 apiError(ctx, http.StatusInternalServerError, err) 138 } 139 return 140 } 141 142 ctx.Status(http.StatusCreated) 143 } 144 145 // DeletePackage deletes the specific generic package. 146 func DeletePackage(ctx *context.Context) { 147 err := packages_service.RemovePackageVersionByNameAndVersion( 148 ctx, 149 ctx.Doer, 150 &packages_service.PackageInfo{ 151 Owner: ctx.Package.Owner, 152 PackageType: packages_model.TypeGeneric, 153 Name: ctx.Params("packagename"), 154 Version: ctx.Params("packageversion"), 155 }, 156 ) 157 if err != nil { 158 if err == packages_model.ErrPackageNotExist { 159 apiError(ctx, http.StatusNotFound, err) 160 return 161 } 162 apiError(ctx, http.StatusInternalServerError, err) 163 return 164 } 165 166 ctx.Status(http.StatusNoContent) 167 } 168 169 // DeletePackageFile deletes the specific file of a generic package. 170 func DeletePackageFile(ctx *context.Context) { 171 pv, pf, err := func() (*packages_model.PackageVersion, *packages_model.PackageFile, error) { 172 pv, err := packages_model.GetVersionByNameAndVersion(ctx, ctx.Package.Owner.ID, packages_model.TypeGeneric, ctx.Params("packagename"), ctx.Params("packageversion")) 173 if err != nil { 174 return nil, nil, err 175 } 176 177 pf, err := packages_model.GetFileForVersionByName(ctx, pv.ID, ctx.Params("filename"), packages_model.EmptyFileKey) 178 if err != nil { 179 return nil, nil, err 180 } 181 182 return pv, pf, nil 183 }() 184 if err != nil { 185 if err == packages_model.ErrPackageNotExist || err == packages_model.ErrPackageFileNotExist { 186 apiError(ctx, http.StatusNotFound, err) 187 return 188 } 189 apiError(ctx, http.StatusInternalServerError, err) 190 return 191 } 192 193 pfs, err := packages_model.GetFilesByVersionID(ctx, pv.ID) 194 if err != nil { 195 apiError(ctx, http.StatusInternalServerError, err) 196 return 197 } 198 199 if len(pfs) == 1 { 200 if err := packages_service.RemovePackageVersion(ctx, ctx.Doer, pv); err != nil { 201 apiError(ctx, http.StatusInternalServerError, err) 202 return 203 } 204 } else { 205 if err := packages_service.DeletePackageFile(ctx, pf); err != nil { 206 apiError(ctx, http.StatusInternalServerError, err) 207 return 208 } 209 } 210 211 ctx.Status(http.StatusNoContent) 212 }