package llamaswap import ( "bytes" "fmt" "mime/multipart" "strings" ) // upstreamPath builds a path through llama-swap's generic /upstream// // passthrough, which pins the model (triggering the normal load/swap queue) // and forwards the remaining path to the upstream verbatim. This is how the // provider reaches upstreams whose native APIs carry no routable `model` // field (rembg, mediautils, WhisperX, ACE-Step, ...) without needing a // llama-swap route per endpoint (ADR-0020). // // Why reject rather than escape: same rationale as Unload — model ids // legitimately contain ":" but never path-structure characters, and escaping // would mask a config error instead of surfacing it. func upstreamPath(model, rest string) (string, error) { if strings.TrimSpace(model) == "" { return "", fmt.Errorf("llama-swap: upstream call requires a model id") } if strings.ContainsAny(model, "/?#") || strings.Contains(model, "..") { return "", fmt.Errorf("llama-swap: invalid model id %q for upstream call (contains a path separator)", model) } if !strings.HasPrefix(rest, "/") { rest = "/" + rest } // rest may embed SERVER-SUPPLIED components (e.g. ACE-Step's result // file URL) — refuse dot-dot segments and absolute-URL smuggling so a // hostile/buggy upstream cannot redirect the follow-up request at // another proxy endpoint (/api/models/unload, ...). if strings.Contains(rest, "..") || strings.Contains(rest, "://") { return "", fmt.Errorf("llama-swap: invalid upstream path %q (dot-dot or scheme)", rest) } return "/upstream/" + model + rest, nil } // filePart is the single file entry of a media multipart form. type filePart struct { field string // form field name ("file", "audio_file", ...) filename string // already sanitized data []byte } // buildMultipart assembles a one-file multipart body: the file part first, // then the given fields (optional fields skipped when empty, matching // writeFormFields). wrap labels errors. Returns the body and its content // type. func buildMultipart(wrap string, file filePart, fields []formField) (*bytes.Buffer, string, error) { var buf bytes.Buffer w := multipart.NewWriter(&buf) fw, err := w.CreateFormFile(file.field, file.filename) if err != nil { return nil, "", fmt.Errorf("llama-swap: %s: %w", wrap, err) } if _, err := fw.Write(file.data); err != nil { return nil, "", fmt.Errorf("llama-swap: %s: %w", wrap, err) } if err := writeFormFields(w, wrap, fields); err != nil { return nil, "", err } if err := w.Close(); err != nil { return nil, "", fmt.Errorf("llama-swap: %s: %w", wrap, err) } return &buf, w.FormDataContentType(), nil }