Files
ARTEX/server/task_templates.go
BaaRai e233c02314 Translate all Chinese text to English
Comments, doc comments, UI labels, log and error messages, LLM prompt
strings, markdown docs, test data and assertions. Identifiers, enum
keys, routes, placeholders and code structure are unchanged.

Notes:
- docs/漏洞流量证据.md renamed to docs/vulnerability-traffic-evidence.md
- tests that exercised CJK specific handling (GBK zip names, Chinese
  full text search) now use ASCII equivalents; go build passes and the
  test suite matches the pre translation baseline (remaining failures
  need a PostgreSQL DSN and fail identically on the original)
- frontend mention syntax now uses English kinds (finding, asset, ...)
  matching the backend, chat mentions tests pass
2026-10-08 23:14:34 +02:00

217 lines
6.1 KiB
Go

package server
import (
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"strings"
"unicode/utf8"
"github.com/Autumn-27/artex/db"
)
const maxTaskTemplateRequestBytes = 512 << 10
// taskTemplateRequest uses pointers so PATCH can distinguish omitted fields from
// explicit empty values. Empty values are still rejected by the DB validator.
// category_id / intercept_rules presence is detected via the raw key set (see
// decodeTaskTemplateRequest) so a PATCH can clear category to null.
type taskTemplateRequest struct {
Name *string `json:"name"`
Description *string `json:"description"`
Goal *string `json:"goal"`
CategoryID *int64 `json:"category_id"`
InterceptRules []taskInterceptRuleReq `json:"intercept_rules"`
}
// decodeTaskTemplateRequest decodes the body into req and returns the set of
// top-level keys present in the JSON (for PATCH presence detection).
func decodeTaskTemplateRequest(w http.ResponseWriter, r *http.Request, req *taskTemplateRequest) (map[string]struct{}, bool) {
r.Body = http.MaxBytesReader(w, r.Body, maxTaskTemplateRequestBytes)
body, err := io.ReadAll(r.Body)
if err != nil {
var tooLarge *http.MaxBytesError
if errors.As(err, &tooLarge) {
writeErr(w, http.StatusRequestEntityTooLarge, "request body too large")
} else {
writeErr(w, http.StatusBadRequest, err.Error())
}
return nil, false
}
var raw map[string]json.RawMessage
if err := json.Unmarshal(body, &raw); err != nil {
writeErr(w, http.StatusBadRequest, err.Error())
return nil, false
}
if err := json.Unmarshal(body, req); err != nil {
writeErr(w, http.StatusBadRequest, err.Error())
return nil, false
}
present := make(map[string]struct{}, len(raw))
for k := range raw {
present[k] = struct{}{}
}
return present, true
}
func validateTaskTemplateRequest(req taskTemplateRequest) error {
checks := []struct {
name string
value *string
limit int
}{
{name: "name", value: req.Name, limit: db.MaxTaskTemplateNameRunes},
{name: "description", value: req.Description, limit: db.MaxTaskTemplateTextRunes},
{name: "goal", value: req.Goal, limit: db.MaxTaskTemplateTextRunes},
}
for _, check := range checks {
if check.value == nil {
continue
}
value := strings.TrimSpace(*check.value)
if check.name == "name" {
value = strings.Join(strings.Fields(value), " ")
}
if utf8.RuneCountInString(value) > check.limit {
return fmt.Errorf("%s must be at most %d characters", check.name, check.limit)
}
}
return nil
}
func writeTaskTemplateErr(w http.ResponseWriter, err error) {
switch {
case errors.Is(err, db.ErrTaskTemplateInvalid):
writeErr(w, http.StatusBadRequest, err.Error())
case errors.Is(err, db.ErrTaskTemplateNameConflict):
writeErr(w, http.StatusConflict, "template name already exists")
case errors.Is(err, db.ErrTaskTemplateNotFound):
writeErr(w, http.StatusNotFound, "task template not found")
default:
writeErr(w, http.StatusInternalServerError, err.Error())
}
}
func (s *Server) pgListTaskTemplates(w http.ResponseWriter, _ *http.Request) {
pg := s.pg(w)
if pg == nil {
return
}
templates, err := pg.ListTaskTemplates()
if err != nil {
writeErr(w, http.StatusInternalServerError, err.Error())
return
}
writeJSON(w, http.StatusOK, map[string]any{"templates": templates})
}
func (s *Server) pgCreateTaskTemplate(w http.ResponseWriter, r *http.Request) {
pg := s.pg(w)
if pg == nil {
return
}
var req taskTemplateRequest
if _, ok := decodeTaskTemplateRequest(w, r, &req); !ok {
return
}
if err := validateTaskTemplateRequest(req); err != nil {
writeErr(w, http.StatusBadRequest, err.Error())
return
}
rules, err := buildTaskInterceptRules(req.InterceptRules)
if err != nil {
writeErr(w, http.StatusBadRequest, "invalid intercept/allow rule: "+err.Error())
return
}
template, err := pg.CreateTaskTemplate(db.TaskTemplateInput{
Name: stringValue(req.Name),
Description: stringValue(req.Description),
Goal: stringValue(req.Goal),
CategoryID: req.CategoryID,
InterceptRules: rules,
})
if err != nil {
writeTaskTemplateErr(w, err)
return
}
writeJSON(w, http.StatusCreated, template)
}
func (s *Server) pgUpdateTaskTemplate(w http.ResponseWriter, r *http.Request) {
pg := s.pg(w)
if pg == nil {
return
}
id, ok := pathInt(r, "id")
if !ok {
writeErr(w, http.StatusBadRequest, "bad task template id")
return
}
var req taskTemplateRequest
present, ok := decodeTaskTemplateRequest(w, r, &req)
if !ok {
return
}
if err := validateTaskTemplateRequest(req); err != nil {
writeErr(w, http.StatusBadRequest, err.Error())
return
}
_, catPresent := present["category_id"]
_, rulesPresent := present["intercept_rules"]
if req.Name == nil && req.Description == nil && req.Goal == nil && !catPresent && !rulesPresent {
writeErr(w, http.StatusBadRequest, "at least one of name, description, goal, category_id, or intercept_rules is required")
return
}
patch := db.TaskTemplatePatch{Name: req.Name, Description: req.Description, Goal: req.Goal}
if catPresent {
patch.SetCategoryID = true
patch.CategoryID = req.CategoryID
}
if rulesPresent {
rules, err := buildTaskInterceptRules(req.InterceptRules)
if err != nil {
writeErr(w, http.StatusBadRequest, "invalid intercept/allow rules: "+err.Error())
return
}
patch.SetInterceptRules = true
patch.InterceptRules = rules
}
template, err := pg.PatchTaskTemplate(id, patch)
if err != nil {
writeTaskTemplateErr(w, err)
return
}
writeJSON(w, http.StatusOK, template)
}
func (s *Server) pgDeleteTaskTemplate(w http.ResponseWriter, r *http.Request) {
pg := s.pg(w)
if pg == nil {
return
}
id, ok := pathInt(r, "id")
if !ok {
writeErr(w, http.StatusBadRequest, "bad task template id")
return
}
deleted, err := pg.DeleteTaskTemplate(id)
if err != nil {
writeErr(w, http.StatusInternalServerError, err.Error())
return
}
if !deleted {
writeErr(w, http.StatusNotFound, "task template not found")
return
}
writeJSON(w, http.StatusOK, map[string]any{"deleted": id})
}
func stringValue(value *string) string {
if value == nil {
return ""
}
return *value
}