mirror of
				https://gitee.com/gitea/gitea
				synced 2025-11-04 08:30:25 +08:00 
			
		
		
		
	From issue https://github.com/go-gitea/gitea/issues/27314 When act_runner in `host` mode on Windows. `upload_artifact@v3` actions use `path.join` to generate `itemPath` params when uploading artifact chunk. `itemPath` is encoded as `${artifact_name}\${artifact_path}`. <del>It's twice query escaped from ${artifact_name}/${artifact_path} that joined by Windows slash \.</del> **So we need convert Windows slash to linux**. In https://github.com/go-gitea/gitea/issues/27314, runner shows logs from `upload_artifact@v3` like with `%255C`: ``` [artifact-cases/test-artifact-cases] | ::error::Unexpected response. Unable to upload chunk to http://192.168.31.230:3000/api/actions_pipeline/_apis/pipelines/workflows/6/artifacts/34d628a422db9367c869d3fb36be81f5/upload?itemPath=more-files%255Css.json ``` But in gitea server at the same time, But shows `%5C` ``` 2023/10/27 19:29:51 ...eb/routing/logger.go:102:func1() [I] router: completed PUT /api/actions_pipeline/_apis/pipelines/workflows/6/artifacts/34d628a422db9367c869d3fb36be81f5/upload?itemPath=more-files%5Css.json for 192.168.31.230:55340, 400 Bad Request in 17.6ms @ <autogenerated>:1(actions.artifactRoutes.uploadArtifact-fm) ``` I found `%255C` is escaped by `https://github.com/actions/upload-artifact/blob/main/dist/index.js#L2329`. --------- Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
		
			
				
	
	
		
			84 lines
		
	
	
		
			2.8 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			84 lines
		
	
	
		
			2.8 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
// Copyright 2023 The Gitea Authors. All rights reserved.
 | 
						|
// SPDX-License-Identifier: MIT
 | 
						|
 | 
						|
package actions
 | 
						|
 | 
						|
import (
 | 
						|
	"crypto/md5"
 | 
						|
	"fmt"
 | 
						|
	"net/http"
 | 
						|
	"strconv"
 | 
						|
	"strings"
 | 
						|
 | 
						|
	"code.gitea.io/gitea/models/actions"
 | 
						|
	"code.gitea.io/gitea/modules/log"
 | 
						|
	"code.gitea.io/gitea/modules/util"
 | 
						|
)
 | 
						|
 | 
						|
const (
 | 
						|
	artifactXTfsFileLengthHeader     = "x-tfs-filelength"
 | 
						|
	artifactXActionsResultsMD5Header = "x-actions-results-md5"
 | 
						|
)
 | 
						|
 | 
						|
// The rules are from https://github.com/actions/toolkit/blob/main/packages/artifact/src/internal/path-and-artifact-name-validation.ts#L32
 | 
						|
var invalidArtifactNameChars = strings.Join([]string{"\\", "/", "\"", ":", "<", ">", "|", "*", "?", "\r", "\n"}, "")
 | 
						|
 | 
						|
func validateArtifactName(ctx *ArtifactContext, artifactName string) bool {
 | 
						|
	if strings.ContainsAny(artifactName, invalidArtifactNameChars) {
 | 
						|
		log.Error("Error checking artifact name contains invalid character")
 | 
						|
		ctx.Error(http.StatusBadRequest, "Error checking artifact name contains invalid character")
 | 
						|
		return false
 | 
						|
	}
 | 
						|
	return true
 | 
						|
}
 | 
						|
 | 
						|
func validateRunID(ctx *ArtifactContext) (*actions.ActionTask, int64, bool) {
 | 
						|
	task := ctx.ActionTask
 | 
						|
	runID := ctx.ParamsInt64("run_id")
 | 
						|
	if task.Job.RunID != runID {
 | 
						|
		log.Error("Error runID not match")
 | 
						|
		ctx.Error(http.StatusBadRequest, "run-id does not match")
 | 
						|
		return nil, 0, false
 | 
						|
	}
 | 
						|
	return task, runID, true
 | 
						|
}
 | 
						|
 | 
						|
func validateArtifactHash(ctx *ArtifactContext, artifactName string) bool {
 | 
						|
	paramHash := ctx.Params("artifact_hash")
 | 
						|
	// use artifact name to create upload url
 | 
						|
	artifactHash := fmt.Sprintf("%x", md5.Sum([]byte(artifactName)))
 | 
						|
	if paramHash == artifactHash {
 | 
						|
		return true
 | 
						|
	}
 | 
						|
	log.Error("Invalid artifact hash: %s", paramHash)
 | 
						|
	ctx.Error(http.StatusBadRequest, "Invalid artifact hash")
 | 
						|
	return false
 | 
						|
}
 | 
						|
 | 
						|
func parseArtifactItemPath(ctx *ArtifactContext) (string, string, bool) {
 | 
						|
	// itemPath is generated from upload-artifact action
 | 
						|
	// it's formatted as {artifact_name}/{artfict_path_in_runner}
 | 
						|
	// act_runner in host mode on Windows, itemPath is joined by Windows slash '\'
 | 
						|
	itemPath := util.PathJoinRelX(ctx.Req.URL.Query().Get("itemPath"))
 | 
						|
	artifactName := strings.Split(itemPath, "/")[0]
 | 
						|
	artifactPath := strings.TrimPrefix(itemPath, artifactName+"/")
 | 
						|
	if !validateArtifactHash(ctx, artifactName) {
 | 
						|
		return "", "", false
 | 
						|
	}
 | 
						|
	if !validateArtifactName(ctx, artifactName) {
 | 
						|
		return "", "", false
 | 
						|
	}
 | 
						|
	return artifactName, artifactPath, true
 | 
						|
}
 | 
						|
 | 
						|
// getUploadFileSize returns the size of the file to be uploaded.
 | 
						|
// The raw size is the size of the file as reported by the header X-TFS-FileLength.
 | 
						|
func getUploadFileSize(ctx *ArtifactContext) (int64, int64, error) {
 | 
						|
	contentLength := ctx.Req.ContentLength
 | 
						|
	xTfsLength, _ := strconv.ParseInt(ctx.Req.Header.Get(artifactXTfsFileLengthHeader), 10, 64)
 | 
						|
	if xTfsLength > 0 {
 | 
						|
		return xTfsLength, contentLength, nil
 | 
						|
	}
 | 
						|
	return contentLength, contentLength, nil
 | 
						|
}
 |