code.gitea.io/gitea@v1.22.3/routers/api/actions/artifacts_utils.go (about)

     1  // Copyright 2023 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package actions
     5  
     6  import (
     7  	"crypto/md5"
     8  	"fmt"
     9  	"net/http"
    10  	"strconv"
    11  	"strings"
    12  
    13  	"code.gitea.io/gitea/models/actions"
    14  	"code.gitea.io/gitea/modules/log"
    15  	"code.gitea.io/gitea/modules/util"
    16  )
    17  
    18  const (
    19  	artifactXTfsFileLengthHeader     = "x-tfs-filelength"
    20  	artifactXActionsResultsMD5Header = "x-actions-results-md5"
    21  )
    22  
    23  // The rules are from https://github.com/actions/toolkit/blob/main/packages/artifact/src/internal/path-and-artifact-name-validation.ts#L32
    24  var invalidArtifactNameChars = strings.Join([]string{"\\", "/", "\"", ":", "<", ">", "|", "*", "?", "\r", "\n"}, "")
    25  
    26  func validateArtifactName(ctx *ArtifactContext, artifactName string) bool {
    27  	if strings.ContainsAny(artifactName, invalidArtifactNameChars) {
    28  		log.Error("Error checking artifact name contains invalid character")
    29  		ctx.Error(http.StatusBadRequest, "Error checking artifact name contains invalid character")
    30  		return false
    31  	}
    32  	return true
    33  }
    34  
    35  func validateRunID(ctx *ArtifactContext) (*actions.ActionTask, int64, bool) {
    36  	task := ctx.ActionTask
    37  	runID := ctx.ParamsInt64("run_id")
    38  	if task.Job.RunID != runID {
    39  		log.Error("Error runID not match")
    40  		ctx.Error(http.StatusBadRequest, "run-id does not match")
    41  		return nil, 0, false
    42  	}
    43  	return task, runID, true
    44  }
    45  
    46  func validateRunIDV4(ctx *ArtifactContext, rawRunID string) (*actions.ActionTask, int64, bool) {
    47  	task := ctx.ActionTask
    48  	runID, err := strconv.ParseInt(rawRunID, 10, 64)
    49  	if err != nil || task.Job.RunID != runID {
    50  		log.Error("Error runID not match")
    51  		ctx.Error(http.StatusBadRequest, "run-id does not match")
    52  		return nil, 0, false
    53  	}
    54  	return task, runID, true
    55  }
    56  
    57  func validateArtifactHash(ctx *ArtifactContext, artifactName string) bool {
    58  	paramHash := ctx.Params("artifact_hash")
    59  	// use artifact name to create upload url
    60  	artifactHash := fmt.Sprintf("%x", md5.Sum([]byte(artifactName)))
    61  	if paramHash == artifactHash {
    62  		return true
    63  	}
    64  	log.Error("Invalid artifact hash: %s", paramHash)
    65  	ctx.Error(http.StatusBadRequest, "Invalid artifact hash")
    66  	return false
    67  }
    68  
    69  func parseArtifactItemPath(ctx *ArtifactContext) (string, string, bool) {
    70  	// itemPath is generated from upload-artifact action
    71  	// it's formatted as {artifact_name}/{artfict_path_in_runner}
    72  	// act_runner in host mode on Windows, itemPath is joined by Windows slash '\'
    73  	itemPath := util.PathJoinRelX(ctx.Req.URL.Query().Get("itemPath"))
    74  	artifactName := strings.Split(itemPath, "/")[0]
    75  	artifactPath := strings.TrimPrefix(itemPath, artifactName+"/")
    76  	if !validateArtifactHash(ctx, artifactName) {
    77  		return "", "", false
    78  	}
    79  	if !validateArtifactName(ctx, artifactName) {
    80  		return "", "", false
    81  	}
    82  	return artifactName, artifactPath, true
    83  }
    84  
    85  // getUploadFileSize returns the size of the file to be uploaded.
    86  // The raw size is the size of the file as reported by the header X-TFS-FileLength.
    87  func getUploadFileSize(ctx *ArtifactContext) (int64, int64, error) {
    88  	contentLength := ctx.Req.ContentLength
    89  	xTfsLength, _ := strconv.ParseInt(ctx.Req.Header.Get(artifactXTfsFileLengthHeader), 10, 64)
    90  	if xTfsLength > 0 {
    91  		return xTfsLength, contentLength, nil
    92  	}
    93  	return contentLength, contentLength, nil
    94  }