code.gitea.io/gitea@v1.21.7/routers/api/packages/debian/debian.go (about)

     1  // Copyright 2023 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package debian
     5  
     6  import (
     7  	stdctx "context"
     8  	"errors"
     9  	"fmt"
    10  	"io"
    11  	"net/http"
    12  	"strings"
    13  
    14  	"code.gitea.io/gitea/models/db"
    15  	packages_model "code.gitea.io/gitea/models/packages"
    16  	"code.gitea.io/gitea/modules/context"
    17  	packages_module "code.gitea.io/gitea/modules/packages"
    18  	debian_module "code.gitea.io/gitea/modules/packages/debian"
    19  	"code.gitea.io/gitea/modules/util"
    20  	"code.gitea.io/gitea/routers/api/packages/helper"
    21  	notify_service "code.gitea.io/gitea/services/notify"
    22  	packages_service "code.gitea.io/gitea/services/packages"
    23  	debian_service "code.gitea.io/gitea/services/packages/debian"
    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  func GetRepositoryKey(ctx *context.Context) {
    33  	_, pub, err := debian_service.GetOrCreateKeyPair(ctx, ctx.Package.Owner.ID)
    34  	if err != nil {
    35  		apiError(ctx, http.StatusInternalServerError, err)
    36  		return
    37  	}
    38  
    39  	ctx.ServeContent(strings.NewReader(pub), &context.ServeHeaderOptions{
    40  		ContentType: "application/pgp-keys",
    41  		Filename:    "repository.key",
    42  	})
    43  }
    44  
    45  // https://wiki.debian.org/DebianRepository/Format#A.22Release.22_files
    46  // https://wiki.debian.org/DebianRepository/Format#A.22Packages.22_Indices
    47  func GetRepositoryFile(ctx *context.Context) {
    48  	pv, err := debian_service.GetOrCreateRepositoryVersion(ctx, ctx.Package.Owner.ID)
    49  	if err != nil {
    50  		apiError(ctx, http.StatusInternalServerError, err)
    51  		return
    52  	}
    53  
    54  	key := ctx.Params("distribution")
    55  
    56  	component := ctx.Params("component")
    57  	architecture := strings.TrimPrefix(ctx.Params("architecture"), "binary-")
    58  	if component != "" && architecture != "" {
    59  		key += "|" + component + "|" + architecture
    60  	}
    61  
    62  	s, u, pf, err := packages_service.GetFileStreamByPackageVersion(
    63  		ctx,
    64  		pv,
    65  		&packages_service.PackageFileInfo{
    66  			Filename:     ctx.Params("filename"),
    67  			CompositeKey: key,
    68  		},
    69  	)
    70  	if err != nil {
    71  		if err == packages_model.ErrPackageNotExist || err == packages_model.ErrPackageFileNotExist {
    72  			apiError(ctx, http.StatusNotFound, err)
    73  		} else {
    74  			apiError(ctx, http.StatusInternalServerError, err)
    75  		}
    76  		return
    77  	}
    78  
    79  	helper.ServePackageFile(ctx, s, u, pf)
    80  }
    81  
    82  // https://wiki.debian.org/DebianRepository/Format#indices_acquisition_via_hashsums_.28by-hash.29
    83  func GetRepositoryFileByHash(ctx *context.Context) {
    84  	pv, err := debian_service.GetOrCreateRepositoryVersion(ctx, ctx.Package.Owner.ID)
    85  	if err != nil {
    86  		apiError(ctx, http.StatusInternalServerError, err)
    87  		return
    88  	}
    89  
    90  	algorithm := strings.ToLower(ctx.Params("algorithm"))
    91  	if algorithm == "md5sum" {
    92  		algorithm = "md5"
    93  	}
    94  
    95  	pfs, _, err := packages_model.SearchFiles(ctx, &packages_model.PackageFileSearchOptions{
    96  		VersionID:     pv.ID,
    97  		Hash:          strings.ToLower(ctx.Params("hash")),
    98  		HashAlgorithm: algorithm,
    99  	})
   100  	if err != nil {
   101  		apiError(ctx, http.StatusInternalServerError, err)
   102  		return
   103  	}
   104  	if len(pfs) != 1 {
   105  		apiError(ctx, http.StatusNotFound, nil)
   106  		return
   107  	}
   108  
   109  	s, u, pf, err := packages_service.GetPackageFileStream(ctx, pfs[0])
   110  	if err != nil {
   111  		if errors.Is(err, util.ErrNotExist) {
   112  			apiError(ctx, http.StatusNotFound, err)
   113  		} else {
   114  			apiError(ctx, http.StatusInternalServerError, err)
   115  		}
   116  		return
   117  	}
   118  
   119  	helper.ServePackageFile(ctx, s, u, pf)
   120  }
   121  
   122  func UploadPackageFile(ctx *context.Context) {
   123  	distribution := strings.TrimSpace(ctx.Params("distribution"))
   124  	component := strings.TrimSpace(ctx.Params("component"))
   125  	if distribution == "" || component == "" {
   126  		apiError(ctx, http.StatusBadRequest, "invalid distribution or component")
   127  		return
   128  	}
   129  
   130  	upload, close, err := ctx.UploadStream()
   131  	if err != nil {
   132  		apiError(ctx, http.StatusInternalServerError, err)
   133  		return
   134  	}
   135  	if close {
   136  		defer upload.Close()
   137  	}
   138  
   139  	buf, err := packages_module.CreateHashedBufferFromReader(upload)
   140  	if err != nil {
   141  		apiError(ctx, http.StatusInternalServerError, err)
   142  		return
   143  	}
   144  	defer buf.Close()
   145  
   146  	pck, err := debian_module.ParsePackage(buf)
   147  	if err != nil {
   148  		if errors.Is(err, util.ErrInvalidArgument) {
   149  			apiError(ctx, http.StatusBadRequest, err)
   150  		} else {
   151  			apiError(ctx, http.StatusInternalServerError, err)
   152  		}
   153  		return
   154  	}
   155  
   156  	if _, err := buf.Seek(0, io.SeekStart); err != nil {
   157  		apiError(ctx, http.StatusInternalServerError, err)
   158  		return
   159  	}
   160  
   161  	_, _, err = packages_service.CreatePackageOrAddFileToExisting(
   162  		ctx,
   163  		&packages_service.PackageCreationInfo{
   164  			PackageInfo: packages_service.PackageInfo{
   165  				Owner:       ctx.Package.Owner,
   166  				PackageType: packages_model.TypeDebian,
   167  				Name:        pck.Name,
   168  				Version:     pck.Version,
   169  			},
   170  			Creator:  ctx.Doer,
   171  			Metadata: pck.Metadata,
   172  		},
   173  		&packages_service.PackageFileCreationInfo{
   174  			PackageFileInfo: packages_service.PackageFileInfo{
   175  				Filename:     fmt.Sprintf("%s_%s_%s.deb", pck.Name, pck.Version, pck.Architecture),
   176  				CompositeKey: fmt.Sprintf("%s|%s", distribution, component),
   177  			},
   178  			Creator: ctx.Doer,
   179  			Data:    buf,
   180  			IsLead:  true,
   181  			Properties: map[string]string{
   182  				debian_module.PropertyDistribution: distribution,
   183  				debian_module.PropertyComponent:    component,
   184  				debian_module.PropertyArchitecture: pck.Architecture,
   185  				debian_module.PropertyControl:      pck.Control,
   186  			},
   187  		},
   188  	)
   189  	if err != nil {
   190  		switch err {
   191  		case packages_model.ErrDuplicatePackageVersion, packages_model.ErrDuplicatePackageFile:
   192  			apiError(ctx, http.StatusBadRequest, err)
   193  		case packages_service.ErrQuotaTotalCount, packages_service.ErrQuotaTypeSize, packages_service.ErrQuotaTotalSize:
   194  			apiError(ctx, http.StatusForbidden, err)
   195  		default:
   196  			apiError(ctx, http.StatusInternalServerError, err)
   197  		}
   198  		return
   199  	}
   200  
   201  	if err := debian_service.BuildSpecificRepositoryFiles(ctx, ctx.Package.Owner.ID, distribution, component, pck.Architecture); err != nil {
   202  		apiError(ctx, http.StatusInternalServerError, err)
   203  		return
   204  	}
   205  
   206  	ctx.Status(http.StatusCreated)
   207  }
   208  
   209  func DownloadPackageFile(ctx *context.Context) {
   210  	name := ctx.Params("name")
   211  	version := ctx.Params("version")
   212  
   213  	s, u, pf, err := packages_service.GetFileStreamByPackageNameAndVersion(
   214  		ctx,
   215  		&packages_service.PackageInfo{
   216  			Owner:       ctx.Package.Owner,
   217  			PackageType: packages_model.TypeDebian,
   218  			Name:        name,
   219  			Version:     version,
   220  		},
   221  		&packages_service.PackageFileInfo{
   222  			Filename:     fmt.Sprintf("%s_%s_%s.deb", name, version, ctx.Params("architecture")),
   223  			CompositeKey: fmt.Sprintf("%s|%s", ctx.Params("distribution"), ctx.Params("component")),
   224  		},
   225  	)
   226  	if err != nil {
   227  		if errors.Is(err, util.ErrNotExist) {
   228  			apiError(ctx, http.StatusNotFound, err)
   229  		} else {
   230  			apiError(ctx, http.StatusInternalServerError, err)
   231  		}
   232  		return
   233  	}
   234  
   235  	helper.ServePackageFile(ctx, s, u, pf, &context.ServeHeaderOptions{
   236  		ContentType:  "application/vnd.debian.binary-package",
   237  		Filename:     pf.Name,
   238  		LastModified: pf.CreatedUnix.AsLocalTime(),
   239  	})
   240  }
   241  
   242  func DeletePackageFile(ctx *context.Context) {
   243  	distribution := ctx.Params("distribution")
   244  	component := ctx.Params("component")
   245  	name := ctx.Params("name")
   246  	version := ctx.Params("version")
   247  	architecture := ctx.Params("architecture")
   248  
   249  	owner := ctx.Package.Owner
   250  
   251  	var pd *packages_model.PackageDescriptor
   252  
   253  	err := db.WithTx(ctx, func(ctx stdctx.Context) error {
   254  		pv, err := packages_model.GetVersionByNameAndVersion(ctx, owner.ID, packages_model.TypeDebian, name, version)
   255  		if err != nil {
   256  			return err
   257  		}
   258  
   259  		pf, err := packages_model.GetFileForVersionByName(
   260  			ctx,
   261  			pv.ID,
   262  			fmt.Sprintf("%s_%s_%s.deb", name, version, architecture),
   263  			fmt.Sprintf("%s|%s", distribution, component),
   264  		)
   265  		if err != nil {
   266  			return err
   267  		}
   268  
   269  		if err := packages_service.DeletePackageFile(ctx, pf); err != nil {
   270  			return err
   271  		}
   272  
   273  		has, err := packages_model.HasVersionFileReferences(ctx, pv.ID)
   274  		if err != nil {
   275  			return err
   276  		}
   277  		if !has {
   278  			pd, err = packages_model.GetPackageDescriptor(ctx, pv)
   279  			if err != nil {
   280  				return err
   281  			}
   282  
   283  			if err := packages_service.DeletePackageVersionAndReferences(ctx, pv); err != nil {
   284  				return err
   285  			}
   286  		}
   287  
   288  		return nil
   289  	})
   290  	if err != nil {
   291  		if errors.Is(err, util.ErrNotExist) {
   292  			apiError(ctx, http.StatusNotFound, err)
   293  		} else {
   294  			apiError(ctx, http.StatusInternalServerError, err)
   295  		}
   296  		return
   297  	}
   298  
   299  	if pd != nil {
   300  		notify_service.PackageDelete(ctx, ctx.Doer, pd)
   301  	}
   302  
   303  	if err := debian_service.BuildSpecificRepositoryFiles(ctx, ctx.Package.Owner.ID, distribution, component, architecture); err != nil {
   304  		apiError(ctx, http.StatusInternalServerError, err)
   305  		return
   306  	}
   307  
   308  	ctx.Status(http.StatusNoContent)
   309  }