cli/internal/bugseti/ethics_guard.go

237 lines
4.6 KiB
Go
Raw Normal View History

// Package bugseti provides services for the BugSETI distributed bug fixing application.
package bugseti
import (
"bytes"
"context"
"encoding/xml"
"strings"
"sync"
"time"
)
const (
maxEnvRunes = 512
maxTitleRunes = 160
maxNotificationRunes = 200
maxSummaryRunes = 4000
maxBodyRunes = 8000
maxFileRunes = 260
)
type EthicsGuard struct {
Modal string
Axioms map[string]any
Loaded bool
}
var (
ethicsGuardMu sync.Mutex
ethicsGuard *EthicsGuard
ethicsGuardRoot string
)
func getEthicsGuard(ctx context.Context) *EthicsGuard {
return getEthicsGuardWithRoot(ctx, "")
}
func getEthicsGuardWithRoot(ctx context.Context, rootHint string) *EthicsGuard {
rootHint = strings.TrimSpace(rootHint)
ethicsGuardMu.Lock()
defer ethicsGuardMu.Unlock()
if ethicsGuard != nil && ethicsGuardRoot == rootHint {
return ethicsGuard
}
guard := loadEthicsGuard(ctx, rootHint)
if guard == nil {
guard = &EthicsGuard{}
}
ethicsGuard = guard
ethicsGuardRoot = rootHint
if ethicsGuard == nil {
return &EthicsGuard{}
}
return ethicsGuard
}
func guardFromMarketplace(ctx context.Context, client marketplaceClient) *EthicsGuard {
if client == nil {
return &EthicsGuard{}
}
if ctx == nil {
ctx = context.Background()
}
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
ethics, err := client.EthicsCheck(ctx)
if err != nil || ethics == nil {
return &EthicsGuard{}
}
return &EthicsGuard{
Modal: ethics.Modal,
Axioms: ethics.Axioms,
Loaded: true,
}
}
func loadEthicsGuard(ctx context.Context, rootHint string) *EthicsGuard {
if ctx == nil {
ctx = context.Background()
}
ctx, cancel := context.WithTimeout(ctx, 2*time.Second)
defer cancel()
client, err := newMarketplaceClient(ctx, rootHint)
if err != nil {
return &EthicsGuard{}
}
defer client.Close()
ethics, err := client.EthicsCheck(ctx)
if err != nil || ethics == nil {
return &EthicsGuard{}
}
return &EthicsGuard{
Modal: ethics.Modal,
Axioms: ethics.Axioms,
Loaded: true,
}
}
func (g *EthicsGuard) SanitizeEnv(value string) string {
return sanitizeInline(value, maxEnvRunes)
}
func (g *EthicsGuard) SanitizeTitle(value string) string {
return sanitizeInline(value, maxTitleRunes)
}
func (g *EthicsGuard) SanitizeNotification(value string) string {
return sanitizeInline(value, maxNotificationRunes)
}
func (g *EthicsGuard) SanitizeSummary(value string) string {
return sanitizeMultiline(value, maxSummaryRunes)
}
func (g *EthicsGuard) SanitizeBody(value string) string {
return sanitizeMultiline(value, maxBodyRunes)
}
func (g *EthicsGuard) SanitizeFiles(values []string) []string {
if len(values) == 0 {
return nil
}
seen := make(map[string]bool)
clean := make([]string, 0, len(values))
for _, value := range values {
trimmed := sanitizeInline(value, maxFileRunes)
if trimmed == "" {
continue
}
if strings.Contains(trimmed, "..") {
continue
}
if seen[trimmed] {
continue
}
seen[trimmed] = true
clean = append(clean, trimmed)
}
return clean
}
func (g *EthicsGuard) SanitizeList(values []string, maxRunes int) []string {
if len(values) == 0 {
return nil
}
if maxRunes <= 0 {
maxRunes = maxTitleRunes
}
clean := make([]string, 0, len(values))
for _, value := range values {
trimmed := sanitizeInline(value, maxRunes)
if trimmed == "" {
continue
}
clean = append(clean, trimmed)
}
return clean
}
func sanitizeInline(input string, maxRunes int) string {
return sanitizeText(input, maxRunes, false)
}
func sanitizeMultiline(input string, maxRunes int) string {
return sanitizeText(input, maxRunes, true)
}
func sanitizeText(input string, maxRunes int, allowNewlines bool) string {
if input == "" {
return ""
}
if maxRunes <= 0 {
maxRunes = maxSummaryRunes
}
var b strings.Builder
count := 0
for _, r := range input {
if r == '\r' {
continue
}
if r == '\n' {
if allowNewlines {
b.WriteRune(r)
count++
} else {
b.WriteRune(' ')
count++
}
if count >= maxRunes {
break
}
continue
}
if r == '\t' {
b.WriteRune(' ')
count++
if count >= maxRunes {
break
}
continue
}
if r < 0x20 || r == 0x7f {
continue
}
b.WriteRune(r)
count++
if count >= maxRunes {
break
}
}
return strings.TrimSpace(b.String())
}
func escapeAppleScript(value string) string {
value = strings.ReplaceAll(value, "\\", "\\\\")
value = strings.ReplaceAll(value, "\"", "\\\"")
return value
}
func escapePowerShellXML(value string) string {
var buffer bytes.Buffer
_ = xml.EscapeText(&buffer, []byte(value))
return buffer.String()
}