150 lines
6.1 KiB
Go
150 lines
6.1 KiB
Go
package validator
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"browser.local/platform/domain"
|
|
)
|
|
|
|
const MaxArtifactDownloadBytes = MaxArtifactChunkBytes
|
|
|
|
func ValidateArtifactDownloadReferenceRequest(request domain.ArtifactDownloadReferenceRequest) error {
|
|
var violations []string
|
|
violations = appendRequired(violations, "artifactId", request.ArtifactID)
|
|
violations = appendArtifactIDViolations(violations, request.ArtifactID)
|
|
return finish(violations)
|
|
}
|
|
|
|
func ValidateArtifactDownloadReference(reference domain.ArtifactDownloadReference) error {
|
|
var violations []string
|
|
violations = appendRequired(violations, "artifactId", reference.ArtifactID)
|
|
violations = appendRequired(violations, "ownerId", reference.OwnerID)
|
|
violations = appendRequired(violations, "filename", reference.Filename)
|
|
violations = appendRequired(violations, "contentType", reference.ContentType)
|
|
violations = appendRequired(violations, "checksum", reference.Checksum)
|
|
violations = appendRequired(violations, "downloadUrl", reference.DownloadURL)
|
|
violations = appendRequired(violations, "storageBehavior", reference.StorageBehavior)
|
|
violations = appendArtifactIDViolations(violations, reference.ArtifactID)
|
|
if !validArtifactOwnerKind(reference.OwnerKind) {
|
|
violations = append(violations, "ownerKind is invalid")
|
|
}
|
|
if reference.State != domain.ArtifactStateAvailable {
|
|
violations = append(violations, "state must be available")
|
|
}
|
|
if reference.SizeBytes <= 0 {
|
|
violations = append(violations, "sizeBytes must be positive")
|
|
}
|
|
if reference.Checksum != "" && !validSHA256Checksum(reference.Checksum) {
|
|
violations = append(violations, "checksum must be sha256:<hex>")
|
|
}
|
|
if reference.ChunkSizeBytes <= 0 || reference.ChunkSizeBytes > MaxArtifactDownloadBytes {
|
|
violations = append(violations, fmt.Sprintf("chunkSizeBytes must be between 1 and %d", MaxArtifactDownloadBytes))
|
|
}
|
|
if reference.ExpiresAt.IsZero() {
|
|
violations = append(violations, "expiresAt is required")
|
|
}
|
|
for _, value := range []fieldString{
|
|
{field: "filename", value: reference.Filename},
|
|
{field: "contentType", value: reference.ContentType},
|
|
{field: "downloadUrl", value: reference.DownloadURL},
|
|
{field: "storageBehavior", value: reference.StorageBehavior},
|
|
} {
|
|
if unsafeArtifactString(value.value) {
|
|
violations = append(violations, value.field+" contains unsafe content")
|
|
}
|
|
}
|
|
if !strings.HasPrefix(reference.DownloadURL, "/api/v1/artifacts/") || !strings.HasSuffix(reference.DownloadURL, "/content") {
|
|
violations = append(violations, "downloadUrl must be a platform artifact content route")
|
|
}
|
|
return finish(violations)
|
|
}
|
|
|
|
func ValidateArtifactContentRequest(request domain.ArtifactContentRequest) error {
|
|
var violations []string
|
|
violations = appendRequired(violations, "artifactId", request.ArtifactID)
|
|
violations = appendArtifactIDViolations(violations, request.ArtifactID)
|
|
if request.Offset < 0 {
|
|
violations = append(violations, "offset must not be negative")
|
|
}
|
|
if request.Limit < 0 {
|
|
violations = append(violations, "limit must not be negative")
|
|
}
|
|
if request.Limit > MaxArtifactDownloadBytes {
|
|
violations = append(violations, fmt.Sprintf("limit must not exceed %d", MaxArtifactDownloadBytes))
|
|
}
|
|
return finish(violations)
|
|
}
|
|
|
|
func ValidateArtifactContent(content domain.ArtifactContent) error {
|
|
content = domain.CopyArtifactContent(content)
|
|
var violations []string
|
|
violations = appendRequired(violations, "artifactId", content.ArtifactID)
|
|
violations = appendRequired(violations, "filename", content.Filename)
|
|
violations = appendRequired(violations, "contentType", content.ContentType)
|
|
violations = appendRequired(violations, "checksum", content.Checksum)
|
|
violations = appendRequired(violations, "contentChecksum", content.ContentChecksum)
|
|
violations = appendArtifactIDViolations(violations, content.ArtifactID)
|
|
if content.Offset < 0 {
|
|
violations = append(violations, "offset must not be negative")
|
|
}
|
|
if content.SizeBytes < 0 {
|
|
violations = append(violations, "sizeBytes must not be negative")
|
|
}
|
|
if content.TotalSizeBytes <= 0 {
|
|
violations = append(violations, "totalSizeBytes must be positive")
|
|
}
|
|
maxContentBytes := int64(MaxArtifactDownloadBytes)
|
|
if !content.Partial {
|
|
maxContentBytes = MaxArtifactBytes
|
|
}
|
|
if content.SizeBytes > maxContentBytes {
|
|
violations = append(violations, fmt.Sprintf("sizeBytes must not exceed %d", maxContentBytes))
|
|
}
|
|
if content.Offset+content.SizeBytes > content.TotalSizeBytes {
|
|
violations = append(violations, "range exceeds artifact size")
|
|
}
|
|
if content.Checksum != "" && !validSHA256Checksum(content.Checksum) {
|
|
violations = append(violations, "checksum must be sha256:<hex>")
|
|
}
|
|
if content.ContentChecksum != "" && !validSHA256Checksum(content.ContentChecksum) {
|
|
violations = append(violations, "contentChecksum must be sha256:<hex>")
|
|
}
|
|
if content.Payload != nil {
|
|
if int64(len(content.Payload)) != content.SizeBytes {
|
|
violations = append(violations, "payload size must match sizeBytes")
|
|
}
|
|
if content.ContentChecksum != "" && content.ContentChecksum != BytesChecksum(content.Payload) {
|
|
violations = append(violations, "contentChecksum does not match payload")
|
|
}
|
|
}
|
|
for _, value := range []fieldString{
|
|
{field: "filename", value: content.Filename},
|
|
{field: "contentType", value: content.ContentType},
|
|
{field: "storageBehavior", value: content.StorageBehavior},
|
|
} {
|
|
if unsafeArtifactString(value.value) {
|
|
violations = append(violations, value.field+" contains unsafe content")
|
|
}
|
|
}
|
|
return finish(violations)
|
|
}
|
|
|
|
func appendArtifactIDViolations(violations []string, artifactID string) []string {
|
|
trimmed := strings.TrimSpace(artifactID)
|
|
if trimmed == "" {
|
|
return violations
|
|
}
|
|
if trimmed != artifactID || len([]rune(trimmed)) > 120 || strings.Contains(trimmed, "/") || strings.Contains(trimmed, `\`) || strings.Contains(trimmed, "://") || strings.Contains(trimmed, "..") {
|
|
violations = append(violations, "artifactId is invalid")
|
|
}
|
|
if unsafeArtifactString(trimmed) {
|
|
violations = append(violations, "artifactId contains unsafe content")
|
|
}
|
|
return violations
|
|
}
|
|
|
|
func unsafeArtifactString(value string) bool {
|
|
return containsUnsafeRuntimeSecret(value) || looksLikeRawHostPath(value) || strings.Contains(strings.ToLower(value), "file://")
|
|
}
|