code.gitea.io/gitea@v1.21.7/routers/api/packages/npm/npm.go (about) 1 // Copyright 2021 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package npm 5 6 import ( 7 "bytes" 8 std_ctx "context" 9 "errors" 10 "fmt" 11 "io" 12 "net/http" 13 "strings" 14 15 "code.gitea.io/gitea/models/db" 16 packages_model "code.gitea.io/gitea/models/packages" 17 access_model "code.gitea.io/gitea/models/perm/access" 18 repo_model "code.gitea.io/gitea/models/repo" 19 "code.gitea.io/gitea/models/unit" 20 "code.gitea.io/gitea/modules/context" 21 packages_module "code.gitea.io/gitea/modules/packages" 22 npm_module "code.gitea.io/gitea/modules/packages/npm" 23 "code.gitea.io/gitea/modules/setting" 24 "code.gitea.io/gitea/modules/util" 25 "code.gitea.io/gitea/routers/api/packages/helper" 26 packages_service "code.gitea.io/gitea/services/packages" 27 28 "github.com/hashicorp/go-version" 29 ) 30 31 // errInvalidTagName indicates an invalid tag name 32 var errInvalidTagName = errors.New("The tag name is invalid") 33 34 func apiError(ctx *context.Context, status int, obj any) { 35 helper.LogAndProcessError(ctx, status, obj, func(message string) { 36 ctx.JSON(status, map[string]string{ 37 "error": message, 38 }) 39 }) 40 } 41 42 // packageNameFromParams gets the package name from the url parameters 43 // Variations: /name/, /@scope/name/, /@scope%2Fname/ 44 func packageNameFromParams(ctx *context.Context) string { 45 scope := ctx.Params("scope") 46 id := ctx.Params("id") 47 if scope != "" { 48 return fmt.Sprintf("@%s/%s", scope, id) 49 } 50 return id 51 } 52 53 // PackageMetadata returns the metadata for a single package 54 func PackageMetadata(ctx *context.Context) { 55 packageName := packageNameFromParams(ctx) 56 57 pvs, err := packages_model.GetVersionsByPackageName(ctx, ctx.Package.Owner.ID, packages_model.TypeNpm, packageName) 58 if err != nil { 59 apiError(ctx, http.StatusInternalServerError, err) 60 return 61 } 62 if len(pvs) == 0 { 63 apiError(ctx, http.StatusNotFound, err) 64 return 65 } 66 67 pds, err := packages_model.GetPackageDescriptors(ctx, pvs) 68 if err != nil { 69 apiError(ctx, http.StatusInternalServerError, err) 70 return 71 } 72 73 resp := createPackageMetadataResponse( 74 setting.AppURL+"api/packages/"+ctx.Package.Owner.Name+"/npm", 75 pds, 76 ) 77 78 ctx.JSON(http.StatusOK, resp) 79 } 80 81 // DownloadPackageFile serves the content of a package 82 func DownloadPackageFile(ctx *context.Context) { 83 packageName := packageNameFromParams(ctx) 84 packageVersion := ctx.Params("version") 85 filename := ctx.Params("filename") 86 87 s, u, pf, err := packages_service.GetFileStreamByPackageNameAndVersion( 88 ctx, 89 &packages_service.PackageInfo{ 90 Owner: ctx.Package.Owner, 91 PackageType: packages_model.TypeNpm, 92 Name: packageName, 93 Version: packageVersion, 94 }, 95 &packages_service.PackageFileInfo{ 96 Filename: filename, 97 }, 98 ) 99 if err != nil { 100 if err == packages_model.ErrPackageNotExist || err == packages_model.ErrPackageFileNotExist { 101 apiError(ctx, http.StatusNotFound, err) 102 return 103 } 104 apiError(ctx, http.StatusInternalServerError, err) 105 return 106 } 107 108 helper.ServePackageFile(ctx, s, u, pf) 109 } 110 111 // DownloadPackageFileByName finds the version and serves the contents of a package 112 func DownloadPackageFileByName(ctx *context.Context) { 113 filename := ctx.Params("filename") 114 115 pvs, _, err := packages_model.SearchVersions(ctx, &packages_model.PackageSearchOptions{ 116 OwnerID: ctx.Package.Owner.ID, 117 Type: packages_model.TypeNpm, 118 Name: packages_model.SearchValue{ 119 ExactMatch: true, 120 Value: packageNameFromParams(ctx), 121 }, 122 HasFileWithName: filename, 123 IsInternal: util.OptionalBoolFalse, 124 }) 125 if err != nil { 126 apiError(ctx, http.StatusInternalServerError, err) 127 return 128 } 129 if len(pvs) != 1 { 130 apiError(ctx, http.StatusNotFound, nil) 131 return 132 } 133 134 s, u, pf, err := packages_service.GetFileStreamByPackageVersion( 135 ctx, 136 pvs[0], 137 &packages_service.PackageFileInfo{ 138 Filename: filename, 139 }, 140 ) 141 if err != nil { 142 if err == packages_model.ErrPackageFileNotExist { 143 apiError(ctx, http.StatusNotFound, err) 144 return 145 } 146 apiError(ctx, http.StatusInternalServerError, err) 147 return 148 } 149 150 helper.ServePackageFile(ctx, s, u, pf) 151 } 152 153 // UploadPackage creates a new package 154 func UploadPackage(ctx *context.Context) { 155 npmPackage, err := npm_module.ParsePackage(ctx.Req.Body) 156 if err != nil { 157 if errors.Is(err, util.ErrInvalidArgument) { 158 apiError(ctx, http.StatusBadRequest, err) 159 } else { 160 apiError(ctx, http.StatusInternalServerError, err) 161 } 162 return 163 } 164 165 repo, err := repo_model.GetRepositoryByURL(ctx, npmPackage.Metadata.Repository.URL) 166 if err == nil { 167 canWrite := repo.OwnerID == ctx.Doer.ID 168 169 if !canWrite { 170 perms, err := access_model.GetUserRepoPermission(ctx, repo, ctx.Doer) 171 if err != nil { 172 apiError(ctx, http.StatusInternalServerError, err) 173 return 174 } 175 176 canWrite = perms.CanWrite(unit.TypePackages) 177 } 178 179 if !canWrite { 180 apiError(ctx, http.StatusForbidden, "no permission to upload this package") 181 return 182 } 183 } 184 185 buf, err := packages_module.CreateHashedBufferFromReader(bytes.NewReader(npmPackage.Data)) 186 if err != nil { 187 apiError(ctx, http.StatusInternalServerError, err) 188 return 189 } 190 defer buf.Close() 191 192 pv, _, err := packages_service.CreatePackageAndAddFile( 193 ctx, 194 &packages_service.PackageCreationInfo{ 195 PackageInfo: packages_service.PackageInfo{ 196 Owner: ctx.Package.Owner, 197 PackageType: packages_model.TypeNpm, 198 Name: npmPackage.Name, 199 Version: npmPackage.Version, 200 }, 201 SemverCompatible: true, 202 Creator: ctx.Doer, 203 Metadata: npmPackage.Metadata, 204 }, 205 &packages_service.PackageFileCreationInfo{ 206 PackageFileInfo: packages_service.PackageFileInfo{ 207 Filename: npmPackage.Filename, 208 }, 209 Creator: ctx.Doer, 210 Data: buf, 211 IsLead: true, 212 }, 213 ) 214 if err != nil { 215 switch err { 216 case packages_model.ErrDuplicatePackageVersion: 217 apiError(ctx, http.StatusBadRequest, err) 218 case packages_service.ErrQuotaTotalCount, packages_service.ErrQuotaTypeSize, packages_service.ErrQuotaTotalSize: 219 apiError(ctx, http.StatusForbidden, err) 220 default: 221 apiError(ctx, http.StatusInternalServerError, err) 222 } 223 return 224 } 225 226 for _, tag := range npmPackage.DistTags { 227 if err := setPackageTag(ctx, tag, pv, false); err != nil { 228 if err == errInvalidTagName { 229 apiError(ctx, http.StatusBadRequest, err) 230 return 231 } 232 apiError(ctx, http.StatusInternalServerError, err) 233 return 234 } 235 } 236 237 if repo != nil { 238 if err := packages_model.SetRepositoryLink(ctx, pv.PackageID, repo.ID); err != nil { 239 apiError(ctx, http.StatusInternalServerError, err) 240 return 241 } 242 } 243 244 ctx.Status(http.StatusCreated) 245 } 246 247 // DeletePreview does nothing 248 // The client tells the server what package version it knows about after deleting a version. 249 func DeletePreview(ctx *context.Context) { 250 ctx.Status(http.StatusOK) 251 } 252 253 // DeletePackageVersion deletes the package version 254 func DeletePackageVersion(ctx *context.Context) { 255 packageName := packageNameFromParams(ctx) 256 packageVersion := ctx.Params("version") 257 258 err := packages_service.RemovePackageVersionByNameAndVersion( 259 ctx, 260 ctx.Doer, 261 &packages_service.PackageInfo{ 262 Owner: ctx.Package.Owner, 263 PackageType: packages_model.TypeNpm, 264 Name: packageName, 265 Version: packageVersion, 266 }, 267 ) 268 if err != nil { 269 if err == packages_model.ErrPackageNotExist { 270 apiError(ctx, http.StatusNotFound, err) 271 return 272 } 273 apiError(ctx, http.StatusInternalServerError, err) 274 return 275 } 276 277 ctx.Status(http.StatusOK) 278 } 279 280 // DeletePackage deletes the package and all versions 281 func DeletePackage(ctx *context.Context) { 282 packageName := packageNameFromParams(ctx) 283 284 pvs, err := packages_model.GetVersionsByPackageName(ctx, ctx.Package.Owner.ID, packages_model.TypeNpm, packageName) 285 if err != nil { 286 apiError(ctx, http.StatusInternalServerError, err) 287 return 288 } 289 290 if len(pvs) == 0 { 291 apiError(ctx, http.StatusNotFound, err) 292 return 293 } 294 295 for _, pv := range pvs { 296 if err := packages_service.RemovePackageVersion(ctx, ctx.Doer, pv); err != nil { 297 apiError(ctx, http.StatusInternalServerError, err) 298 return 299 } 300 } 301 302 ctx.Status(http.StatusOK) 303 } 304 305 // ListPackageTags returns all tags for a package 306 func ListPackageTags(ctx *context.Context) { 307 packageName := packageNameFromParams(ctx) 308 309 pvs, err := packages_model.GetVersionsByPackageName(ctx, ctx.Package.Owner.ID, packages_model.TypeNpm, packageName) 310 if err != nil { 311 apiError(ctx, http.StatusInternalServerError, err) 312 return 313 } 314 315 tags := make(map[string]string) 316 for _, pv := range pvs { 317 pvps, err := packages_model.GetPropertiesByName(ctx, packages_model.PropertyTypeVersion, pv.ID, npm_module.TagProperty) 318 if err != nil { 319 apiError(ctx, http.StatusInternalServerError, err) 320 return 321 } 322 for _, pvp := range pvps { 323 tags[pvp.Value] = pv.Version 324 } 325 } 326 327 ctx.JSON(http.StatusOK, tags) 328 } 329 330 // AddPackageTag adds a tag to the package 331 func AddPackageTag(ctx *context.Context) { 332 packageName := packageNameFromParams(ctx) 333 334 body, err := io.ReadAll(ctx.Req.Body) 335 if err != nil { 336 apiError(ctx, http.StatusInternalServerError, err) 337 return 338 } 339 version := strings.Trim(string(body), "\"") // is as "version" in the body 340 341 pv, err := packages_model.GetVersionByNameAndVersion(ctx, ctx.Package.Owner.ID, packages_model.TypeNpm, packageName, version) 342 if err != nil { 343 if err == packages_model.ErrPackageNotExist { 344 apiError(ctx, http.StatusNotFound, err) 345 return 346 } 347 apiError(ctx, http.StatusInternalServerError, err) 348 return 349 } 350 351 if err := setPackageTag(ctx, ctx.Params("tag"), pv, false); err != nil { 352 if err == errInvalidTagName { 353 apiError(ctx, http.StatusBadRequest, err) 354 return 355 } 356 apiError(ctx, http.StatusInternalServerError, err) 357 return 358 } 359 } 360 361 // DeletePackageTag deletes a package tag 362 func DeletePackageTag(ctx *context.Context) { 363 packageName := packageNameFromParams(ctx) 364 365 pvs, err := packages_model.GetVersionsByPackageName(ctx, ctx.Package.Owner.ID, packages_model.TypeNpm, packageName) 366 if err != nil { 367 apiError(ctx, http.StatusInternalServerError, err) 368 return 369 } 370 371 if len(pvs) != 0 { 372 if err := setPackageTag(ctx, ctx.Params("tag"), pvs[0], true); err != nil { 373 if err == errInvalidTagName { 374 apiError(ctx, http.StatusBadRequest, err) 375 return 376 } 377 apiError(ctx, http.StatusInternalServerError, err) 378 return 379 } 380 } 381 } 382 383 func setPackageTag(ctx std_ctx.Context, tag string, pv *packages_model.PackageVersion, deleteOnly bool) error { 384 if tag == "" { 385 return errInvalidTagName 386 } 387 _, err := version.NewVersion(tag) 388 if err == nil { 389 return errInvalidTagName 390 } 391 392 return db.WithTx(ctx, func(ctx std_ctx.Context) error { 393 pvs, _, err := packages_model.SearchVersions(ctx, &packages_model.PackageSearchOptions{ 394 PackageID: pv.PackageID, 395 Properties: map[string]string{ 396 npm_module.TagProperty: tag, 397 }, 398 IsInternal: util.OptionalBoolFalse, 399 }) 400 if err != nil { 401 return err 402 } 403 404 if len(pvs) == 1 { 405 pvps, err := packages_model.GetPropertiesByName(ctx, packages_model.PropertyTypeVersion, pvs[0].ID, npm_module.TagProperty) 406 if err != nil { 407 return err 408 } 409 410 for _, pvp := range pvps { 411 if pvp.Value == tag { 412 if err := packages_model.DeletePropertyByID(ctx, pvp.ID); err != nil { 413 return err 414 } 415 break 416 } 417 } 418 } 419 420 if !deleteOnly { 421 _, err = packages_model.InsertProperty(ctx, packages_model.PropertyTypeVersion, pv.ID, npm_module.TagProperty, tag) 422 if err != nil { 423 return err 424 } 425 } 426 return nil 427 }) 428 } 429 430 func PackageSearch(ctx *context.Context) { 431 pvs, total, err := packages_model.SearchLatestVersions(ctx, &packages_model.PackageSearchOptions{ 432 OwnerID: ctx.Package.Owner.ID, 433 Type: packages_model.TypeNpm, 434 IsInternal: util.OptionalBoolFalse, 435 Name: packages_model.SearchValue{ 436 ExactMatch: false, 437 Value: ctx.FormTrim("text"), 438 }, 439 Paginator: db.NewAbsoluteListOptions( 440 ctx.FormInt("from"), 441 ctx.FormInt("size"), 442 ), 443 }) 444 if err != nil { 445 apiError(ctx, http.StatusInternalServerError, err) 446 return 447 } 448 449 pds, err := packages_model.GetPackageDescriptors(ctx, pvs) 450 if err != nil { 451 apiError(ctx, http.StatusInternalServerError, err) 452 return 453 } 454 455 resp := createPackageSearchResponse( 456 pds, 457 total, 458 ) 459 460 ctx.JSON(http.StatusOK, resp) 461 }