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  }