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

     1  // Copyright 2023 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package alpine
     5  
     6  import (
     7  	"crypto/x509"
     8  	"encoding/hex"
     9  	"encoding/pem"
    10  	"errors"
    11  	"fmt"
    12  	"io"
    13  	"net/http"
    14  	"strings"
    15  
    16  	packages_model "code.gitea.io/gitea/models/packages"
    17  	"code.gitea.io/gitea/modules/context"
    18  	"code.gitea.io/gitea/modules/json"
    19  	packages_module "code.gitea.io/gitea/modules/packages"
    20  	alpine_module "code.gitea.io/gitea/modules/packages/alpine"
    21  	"code.gitea.io/gitea/modules/util"
    22  	"code.gitea.io/gitea/routers/api/packages/helper"
    23  	packages_service "code.gitea.io/gitea/services/packages"
    24  	alpine_service "code.gitea.io/gitea/services/packages/alpine"
    25  )
    26  
    27  func apiError(ctx *context.Context, status int, obj any) {
    28  	helper.LogAndProcessError(ctx, status, obj, func(message string) {
    29  		ctx.PlainText(status, message)
    30  	})
    31  }
    32  
    33  func GetRepositoryKey(ctx *context.Context) {
    34  	_, pub, err := alpine_service.GetOrCreateKeyPair(ctx, ctx.Package.Owner.ID)
    35  	if err != nil {
    36  		apiError(ctx, http.StatusInternalServerError, err)
    37  		return
    38  	}
    39  
    40  	pubPem, _ := pem.Decode([]byte(pub))
    41  	if pubPem == nil {
    42  		apiError(ctx, http.StatusInternalServerError, "failed to decode private key pem")
    43  		return
    44  	}
    45  
    46  	pubKey, err := x509.ParsePKIXPublicKey(pubPem.Bytes)
    47  	if err != nil {
    48  		apiError(ctx, http.StatusInternalServerError, err)
    49  		return
    50  	}
    51  
    52  	fingerprint, err := util.CreatePublicKeyFingerprint(pubKey)
    53  	if err != nil {
    54  		apiError(ctx, http.StatusInternalServerError, err)
    55  		return
    56  	}
    57  
    58  	ctx.ServeContent(strings.NewReader(pub), &context.ServeHeaderOptions{
    59  		ContentType: "application/x-pem-file",
    60  		Filename:    fmt.Sprintf("%s@%s.rsa.pub", ctx.Package.Owner.LowerName, hex.EncodeToString(fingerprint)),
    61  	})
    62  }
    63  
    64  func GetRepositoryFile(ctx *context.Context) {
    65  	pv, err := alpine_service.GetOrCreateRepositoryVersion(ctx, ctx.Package.Owner.ID)
    66  	if err != nil {
    67  		apiError(ctx, http.StatusInternalServerError, err)
    68  		return
    69  	}
    70  
    71  	s, u, pf, err := packages_service.GetFileStreamByPackageVersion(
    72  		ctx,
    73  		pv,
    74  		&packages_service.PackageFileInfo{
    75  			Filename:     alpine_service.IndexFilename,
    76  			CompositeKey: fmt.Sprintf("%s|%s|%s", ctx.Params("branch"), ctx.Params("repository"), ctx.Params("architecture")),
    77  		},
    78  	)
    79  	if err != nil {
    80  		if errors.Is(err, util.ErrNotExist) {
    81  			apiError(ctx, http.StatusNotFound, err)
    82  		} else {
    83  			apiError(ctx, http.StatusInternalServerError, err)
    84  		}
    85  		return
    86  	}
    87  
    88  	helper.ServePackageFile(ctx, s, u, pf)
    89  }
    90  
    91  func UploadPackageFile(ctx *context.Context) {
    92  	branch := strings.TrimSpace(ctx.Params("branch"))
    93  	repository := strings.TrimSpace(ctx.Params("repository"))
    94  	if branch == "" || repository == "" {
    95  		apiError(ctx, http.StatusBadRequest, "invalid branch or repository")
    96  		return
    97  	}
    98  
    99  	upload, close, err := ctx.UploadStream()
   100  	if err != nil {
   101  		apiError(ctx, http.StatusInternalServerError, err)
   102  		return
   103  	}
   104  	if close {
   105  		defer upload.Close()
   106  	}
   107  
   108  	buf, err := packages_module.CreateHashedBufferFromReader(upload)
   109  	if err != nil {
   110  		apiError(ctx, http.StatusInternalServerError, err)
   111  		return
   112  	}
   113  	defer buf.Close()
   114  
   115  	pck, err := alpine_module.ParsePackage(buf)
   116  	if err != nil {
   117  		if errors.Is(err, util.ErrInvalidArgument) || err == io.EOF {
   118  			apiError(ctx, http.StatusBadRequest, err)
   119  		} else {
   120  			apiError(ctx, http.StatusInternalServerError, err)
   121  		}
   122  		return
   123  	}
   124  
   125  	if _, err := buf.Seek(0, io.SeekStart); err != nil {
   126  		apiError(ctx, http.StatusInternalServerError, err)
   127  		return
   128  	}
   129  
   130  	fileMetadataRaw, err := json.Marshal(pck.FileMetadata)
   131  	if err != nil {
   132  		apiError(ctx, http.StatusInternalServerError, err)
   133  		return
   134  	}
   135  
   136  	_, _, err = packages_service.CreatePackageOrAddFileToExisting(
   137  		ctx,
   138  		&packages_service.PackageCreationInfo{
   139  			PackageInfo: packages_service.PackageInfo{
   140  				Owner:       ctx.Package.Owner,
   141  				PackageType: packages_model.TypeAlpine,
   142  				Name:        pck.Name,
   143  				Version:     pck.Version,
   144  			},
   145  			Creator:  ctx.Doer,
   146  			Metadata: pck.VersionMetadata,
   147  		},
   148  		&packages_service.PackageFileCreationInfo{
   149  			PackageFileInfo: packages_service.PackageFileInfo{
   150  				Filename:     fmt.Sprintf("%s-%s.apk", pck.Name, pck.Version),
   151  				CompositeKey: fmt.Sprintf("%s|%s|%s", branch, repository, pck.FileMetadata.Architecture),
   152  			},
   153  			Creator: ctx.Doer,
   154  			Data:    buf,
   155  			IsLead:  true,
   156  			Properties: map[string]string{
   157  				alpine_module.PropertyBranch:       branch,
   158  				alpine_module.PropertyRepository:   repository,
   159  				alpine_module.PropertyArchitecture: pck.FileMetadata.Architecture,
   160  				alpine_module.PropertyMetadata:     string(fileMetadataRaw),
   161  			},
   162  		},
   163  	)
   164  	if err != nil {
   165  		switch err {
   166  		case packages_model.ErrDuplicatePackageVersion, packages_model.ErrDuplicatePackageFile:
   167  			apiError(ctx, http.StatusBadRequest, err)
   168  		case packages_service.ErrQuotaTotalCount, packages_service.ErrQuotaTypeSize, packages_service.ErrQuotaTotalSize:
   169  			apiError(ctx, http.StatusForbidden, err)
   170  		default:
   171  			apiError(ctx, http.StatusInternalServerError, err)
   172  		}
   173  		return
   174  	}
   175  
   176  	if err := alpine_service.BuildSpecificRepositoryFiles(ctx, ctx.Package.Owner.ID, branch, repository, pck.FileMetadata.Architecture); err != nil {
   177  		apiError(ctx, http.StatusInternalServerError, err)
   178  		return
   179  	}
   180  
   181  	ctx.Status(http.StatusCreated)
   182  }
   183  
   184  func DownloadPackageFile(ctx *context.Context) {
   185  	pfs, _, err := packages_model.SearchFiles(ctx, &packages_model.PackageFileSearchOptions{
   186  		OwnerID:      ctx.Package.Owner.ID,
   187  		PackageType:  packages_model.TypeAlpine,
   188  		Query:        ctx.Params("filename"),
   189  		CompositeKey: fmt.Sprintf("%s|%s|%s", ctx.Params("branch"), ctx.Params("repository"), ctx.Params("architecture")),
   190  	})
   191  	if err != nil {
   192  		apiError(ctx, http.StatusInternalServerError, err)
   193  		return
   194  	}
   195  	if len(pfs) != 1 {
   196  		apiError(ctx, http.StatusNotFound, nil)
   197  		return
   198  	}
   199  
   200  	s, u, pf, err := packages_service.GetPackageFileStream(ctx, pfs[0])
   201  	if err != nil {
   202  		if errors.Is(err, util.ErrNotExist) {
   203  			apiError(ctx, http.StatusNotFound, err)
   204  		} else {
   205  			apiError(ctx, http.StatusInternalServerError, err)
   206  		}
   207  		return
   208  	}
   209  
   210  	helper.ServePackageFile(ctx, s, u, pf)
   211  }
   212  
   213  func DeletePackageFile(ctx *context.Context) {
   214  	branch, repository, architecture := ctx.Params("branch"), ctx.Params("repository"), ctx.Params("architecture")
   215  
   216  	pfs, _, err := packages_model.SearchFiles(ctx, &packages_model.PackageFileSearchOptions{
   217  		OwnerID:      ctx.Package.Owner.ID,
   218  		PackageType:  packages_model.TypeAlpine,
   219  		Query:        ctx.Params("filename"),
   220  		CompositeKey: fmt.Sprintf("%s|%s|%s", branch, repository, architecture),
   221  	})
   222  	if err != nil {
   223  		apiError(ctx, http.StatusInternalServerError, err)
   224  		return
   225  	}
   226  	if len(pfs) != 1 {
   227  		apiError(ctx, http.StatusNotFound, nil)
   228  		return
   229  	}
   230  
   231  	if err := packages_service.RemovePackageFileAndVersionIfUnreferenced(ctx, ctx.Doer, pfs[0]); err != nil {
   232  		if errors.Is(err, util.ErrNotExist) {
   233  			apiError(ctx, http.StatusNotFound, err)
   234  		} else {
   235  			apiError(ctx, http.StatusInternalServerError, err)
   236  		}
   237  		return
   238  	}
   239  
   240  	if err := alpine_service.BuildSpecificRepositoryFiles(ctx, ctx.Package.Owner.ID, branch, repository, architecture); err != nil {
   241  		apiError(ctx, http.StatusInternalServerError, err)
   242  		return
   243  	}
   244  
   245  	ctx.Status(http.StatusNoContent)
   246  }