| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464 |
- // Copyright 2021 The Gitea Authors. All rights reserved.
- // SPDX-License-Identifier: MIT
-
- package npm
-
- import (
- "bytes"
- std_ctx "context"
- "errors"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- "code.gitea.io/gitea/models/db"
- packages_model "code.gitea.io/gitea/models/packages"
- access_model "code.gitea.io/gitea/models/perm/access"
- repo_model "code.gitea.io/gitea/models/repo"
- "code.gitea.io/gitea/models/unit"
- "code.gitea.io/gitea/modules/optional"
- packages_module "code.gitea.io/gitea/modules/packages"
- npm_module "code.gitea.io/gitea/modules/packages/npm"
- "code.gitea.io/gitea/modules/setting"
- "code.gitea.io/gitea/modules/util"
- "code.gitea.io/gitea/routers/api/packages/helper"
- "code.gitea.io/gitea/services/context"
- packages_service "code.gitea.io/gitea/services/packages"
-
- "github.com/hashicorp/go-version"
- )
-
- // errInvalidTagName indicates an invalid tag name
- var errInvalidTagName = errors.New("The tag name is invalid")
-
- func apiError(ctx *context.Context, status int, obj any) {
- message := helper.ProcessErrorForUser(ctx, status, obj)
- ctx.JSON(status, map[string]string{
- "error": message,
- })
- }
-
- // packageNameFromParams gets the package name from the url parameters
- // Variations: /name/, /@scope/name/, /@scope%2Fname/
- func packageNameFromParams(ctx *context.Context) string {
- scope := ctx.PathParam("scope")
- id := ctx.PathParam("id")
- if scope != "" {
- return fmt.Sprintf("@%s/%s", scope, id)
- }
- return id
- }
-
- // PackageMetadata returns the metadata for a single package
- func PackageMetadata(ctx *context.Context) {
- packageName := packageNameFromParams(ctx)
-
- pvs, err := packages_model.GetVersionsByPackageName(ctx, ctx.Package.Owner.ID, packages_model.TypeNpm, packageName)
- if err != nil {
- apiError(ctx, http.StatusInternalServerError, err)
- return
- }
- if len(pvs) == 0 {
- apiError(ctx, http.StatusNotFound, err)
- return
- }
-
- pds, err := packages_model.GetPackageDescriptors(ctx, pvs)
- if err != nil {
- apiError(ctx, http.StatusInternalServerError, err)
- return
- }
-
- resp := createPackageMetadataResponse(
- setting.AppURL+"api/packages/"+ctx.Package.Owner.Name+"/npm",
- pds,
- )
-
- ctx.JSON(http.StatusOK, resp)
- }
-
- // DownloadPackageFile serves the content of a package
- func DownloadPackageFile(ctx *context.Context) {
- packageName := packageNameFromParams(ctx)
- packageVersion := ctx.PathParam("version")
- filename := ctx.PathParam("filename")
-
- s, u, pf, err := packages_service.OpenFileForDownloadByPackageNameAndVersion(
- ctx,
- &packages_service.PackageInfo{
- Owner: ctx.Package.Owner,
- PackageType: packages_model.TypeNpm,
- Name: packageName,
- Version: packageVersion,
- },
- &packages_service.PackageFileInfo{
- Filename: filename,
- },
- ctx.Req.Method,
- )
- if err != nil {
- if errors.Is(err, packages_model.ErrPackageNotExist) || errors.Is(err, packages_model.ErrPackageFileNotExist) {
- apiError(ctx, http.StatusNotFound, err)
- return
- }
- apiError(ctx, http.StatusInternalServerError, err)
- return
- }
-
- helper.ServePackageFile(ctx, s, u, pf)
- }
-
- // DownloadPackageFileByName finds the version and serves the contents of a package
- func DownloadPackageFileByName(ctx *context.Context) {
- filename := ctx.PathParam("filename")
-
- pvs, _, err := packages_model.SearchVersions(ctx, &packages_model.PackageSearchOptions{
- OwnerID: ctx.Package.Owner.ID,
- Type: packages_model.TypeNpm,
- Name: packages_model.SearchValue{
- ExactMatch: true,
- Value: packageNameFromParams(ctx),
- },
- HasFileWithName: filename,
- IsInternal: optional.Some(false),
- })
- if err != nil {
- apiError(ctx, http.StatusInternalServerError, err)
- return
- }
- if len(pvs) != 1 {
- apiError(ctx, http.StatusNotFound, nil)
- return
- }
-
- s, u, pf, err := packages_service.OpenFileForDownloadByPackageVersion(
- ctx,
- pvs[0],
- &packages_service.PackageFileInfo{
- Filename: filename,
- },
- ctx.Req.Method,
- )
- if err != nil {
- if errors.Is(err, packages_model.ErrPackageFileNotExist) {
- apiError(ctx, http.StatusNotFound, err)
- return
- }
- apiError(ctx, http.StatusInternalServerError, err)
- return
- }
-
- helper.ServePackageFile(ctx, s, u, pf)
- }
-
- // UploadPackage creates a new package
- func UploadPackage(ctx *context.Context) {
- npmPackage, err := npm_module.ParsePackage(ctx.Req.Body)
- if err != nil {
- if errors.Is(err, util.ErrInvalidArgument) {
- apiError(ctx, http.StatusBadRequest, err)
- } else {
- apiError(ctx, http.StatusInternalServerError, err)
- }
- return
- }
-
- repo, err := repo_model.GetRepositoryByURLRelax(ctx, npmPackage.Metadata.Repository.URL)
- if err == nil {
- canWrite := repo.OwnerID == ctx.Doer.ID
-
- if !canWrite {
- perms, err := access_model.GetUserRepoPermission(ctx, repo, ctx.Doer)
- if err != nil {
- apiError(ctx, http.StatusInternalServerError, err)
- return
- }
-
- canWrite = perms.CanWrite(unit.TypePackages)
- }
-
- if !canWrite {
- apiError(ctx, http.StatusForbidden, "no permission to upload this package")
- return
- }
- }
-
- buf, err := packages_module.CreateHashedBufferFromReader(bytes.NewReader(npmPackage.Data))
- if err != nil {
- apiError(ctx, http.StatusInternalServerError, err)
- return
- }
- defer buf.Close()
-
- pv, _, err := packages_service.CreatePackageAndAddFile(
- ctx,
- &packages_service.PackageCreationInfo{
- PackageInfo: packages_service.PackageInfo{
- Owner: ctx.Package.Owner,
- PackageType: packages_model.TypeNpm,
- Name: npmPackage.Name,
- Version: npmPackage.Version,
- },
- SemverCompatible: true,
- Creator: ctx.Doer,
- Metadata: npmPackage.Metadata,
- },
- &packages_service.PackageFileCreationInfo{
- PackageFileInfo: packages_service.PackageFileInfo{
- Filename: npmPackage.Filename,
- },
- Creator: ctx.Doer,
- Data: buf,
- IsLead: true,
- },
- )
- if err != nil {
- switch err {
- case packages_model.ErrDuplicatePackageVersion:
- apiError(ctx, http.StatusConflict, err)
- case packages_service.ErrQuotaTotalCount, packages_service.ErrQuotaTypeSize, packages_service.ErrQuotaTotalSize:
- apiError(ctx, http.StatusForbidden, err)
- default:
- apiError(ctx, http.StatusInternalServerError, err)
- }
- return
- }
-
- for _, tag := range npmPackage.DistTags {
- if err := setPackageTag(ctx, tag, pv, false); err != nil {
- if err == errInvalidTagName {
- apiError(ctx, http.StatusBadRequest, err)
- return
- }
- apiError(ctx, http.StatusInternalServerError, err)
- return
- }
- }
-
- if repo != nil {
- if err := packages_model.SetRepositoryLink(ctx, pv.PackageID, repo.ID); err != nil {
- apiError(ctx, http.StatusInternalServerError, err)
- return
- }
- }
-
- ctx.Status(http.StatusCreated)
- }
-
- // DeletePreview does nothing
- // The client tells the server what package version it knows about after deleting a version.
- func DeletePreview(ctx *context.Context) {
- ctx.Status(http.StatusOK)
- }
-
- // DeletePackageVersion deletes the package version
- func DeletePackageVersion(ctx *context.Context) {
- packageName := packageNameFromParams(ctx)
- packageVersion := ctx.PathParam("version")
-
- err := packages_service.RemovePackageVersionByNameAndVersion(
- ctx,
- ctx.Doer,
- &packages_service.PackageInfo{
- Owner: ctx.Package.Owner,
- PackageType: packages_model.TypeNpm,
- Name: packageName,
- Version: packageVersion,
- },
- )
- if err != nil {
- if errors.Is(err, packages_model.ErrPackageNotExist) {
- apiError(ctx, http.StatusNotFound, err)
- return
- }
- apiError(ctx, http.StatusInternalServerError, err)
- return
- }
-
- ctx.Status(http.StatusOK)
- }
-
- // DeletePackage deletes the package and all versions
- func DeletePackage(ctx *context.Context) {
- packageName := packageNameFromParams(ctx)
-
- pvs, err := packages_model.GetVersionsByPackageName(ctx, ctx.Package.Owner.ID, packages_model.TypeNpm, packageName)
- if err != nil {
- apiError(ctx, http.StatusInternalServerError, err)
- return
- }
-
- if len(pvs) == 0 {
- apiError(ctx, http.StatusNotFound, err)
- return
- }
-
- for _, pv := range pvs {
- if err := packages_service.RemovePackageVersion(ctx, ctx.Doer, pv); err != nil {
- apiError(ctx, http.StatusInternalServerError, err)
- return
- }
- }
-
- ctx.Status(http.StatusOK)
- }
-
- // ListPackageTags returns all tags for a package
- func ListPackageTags(ctx *context.Context) {
- packageName := packageNameFromParams(ctx)
-
- pvs, err := packages_model.GetVersionsByPackageName(ctx, ctx.Package.Owner.ID, packages_model.TypeNpm, packageName)
- if err != nil {
- apiError(ctx, http.StatusInternalServerError, err)
- return
- }
-
- tags := make(map[string]string)
- for _, pv := range pvs {
- pvps, err := packages_model.GetPropertiesByName(ctx, packages_model.PropertyTypeVersion, pv.ID, npm_module.TagProperty)
- if err != nil {
- apiError(ctx, http.StatusInternalServerError, err)
- return
- }
- for _, pvp := range pvps {
- tags[pvp.Value] = pv.Version
- }
- }
-
- ctx.JSON(http.StatusOK, tags)
- }
-
- // AddPackageTag adds a tag to the package
- func AddPackageTag(ctx *context.Context) {
- packageName := packageNameFromParams(ctx)
-
- body, err := io.ReadAll(ctx.Req.Body)
- if err != nil {
- apiError(ctx, http.StatusInternalServerError, err)
- return
- }
- version := strings.Trim(string(body), "\"") // is as "version" in the body
-
- pv, err := packages_model.GetVersionByNameAndVersion(ctx, ctx.Package.Owner.ID, packages_model.TypeNpm, packageName, version)
- if err != nil {
- if errors.Is(err, packages_model.ErrPackageNotExist) {
- apiError(ctx, http.StatusNotFound, err)
- return
- }
- apiError(ctx, http.StatusInternalServerError, err)
- return
- }
-
- if err := setPackageTag(ctx, ctx.PathParam("tag"), pv, false); err != nil {
- if err == errInvalidTagName {
- apiError(ctx, http.StatusBadRequest, err)
- return
- }
- apiError(ctx, http.StatusInternalServerError, err)
- return
- }
- }
-
- // DeletePackageTag deletes a package tag
- func DeletePackageTag(ctx *context.Context) {
- packageName := packageNameFromParams(ctx)
-
- pvs, err := packages_model.GetVersionsByPackageName(ctx, ctx.Package.Owner.ID, packages_model.TypeNpm, packageName)
- if err != nil {
- apiError(ctx, http.StatusInternalServerError, err)
- return
- }
-
- if len(pvs) != 0 {
- if err := setPackageTag(ctx, ctx.PathParam("tag"), pvs[0], true); err != nil {
- if err == errInvalidTagName {
- apiError(ctx, http.StatusBadRequest, err)
- return
- }
- apiError(ctx, http.StatusInternalServerError, err)
- return
- }
- }
- }
-
- func setPackageTag(ctx std_ctx.Context, tag string, pv *packages_model.PackageVersion, deleteOnly bool) error {
- if tag == "" {
- return errInvalidTagName
- }
- _, err := version.NewVersion(tag)
- if err == nil {
- return errInvalidTagName
- }
-
- return db.WithTx(ctx, func(ctx std_ctx.Context) error {
- pvs, _, err := packages_model.SearchVersions(ctx, &packages_model.PackageSearchOptions{
- PackageID: pv.PackageID,
- Properties: map[string]string{
- npm_module.TagProperty: tag,
- },
- IsInternal: optional.Some(false),
- })
- if err != nil {
- return err
- }
-
- if len(pvs) == 1 {
- pvps, err := packages_model.GetPropertiesByName(ctx, packages_model.PropertyTypeVersion, pvs[0].ID, npm_module.TagProperty)
- if err != nil {
- return err
- }
-
- for _, pvp := range pvps {
- if pvp.Value == tag {
- if err := packages_model.DeletePropertyByID(ctx, pvp.ID); err != nil {
- return err
- }
- break
- }
- }
- }
-
- if !deleteOnly {
- _, err = packages_model.InsertProperty(ctx, packages_model.PropertyTypeVersion, pv.ID, npm_module.TagProperty, tag)
- if err != nil {
- return err
- }
- }
- return nil
- })
- }
-
- func PackageSearch(ctx *context.Context) {
- pvs, total, err := packages_model.SearchLatestVersions(ctx, &packages_model.PackageSearchOptions{
- OwnerID: ctx.Package.Owner.ID,
- Type: packages_model.TypeNpm,
- IsInternal: optional.Some(false),
- Name: packages_model.SearchValue{
- ExactMatch: false,
- Value: ctx.FormTrim("text"),
- },
- Paginator: db.NewAbsoluteListOptions(
- ctx.FormInt("from"),
- ctx.FormInt("size"),
- ),
- })
- if err != nil {
- apiError(ctx, http.StatusInternalServerError, err)
- return
- }
-
- pds, err := packages_model.GetPackageDescriptors(ctx, pvs)
- if err != nil {
- apiError(ctx, http.StatusInternalServerError, err)
- return
- }
-
- resp := createPackageSearchResponse(
- pds,
- total,
- )
-
- ctx.JSON(http.StatusOK, resp)
- }
|