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  }