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